2020-11-01 08:43:16 +01:00
|
|
|
"""
|
|
|
|
IMAP repository support
|
2016-07-28 00:42:35 +02:00
|
|
|
|
2020-11-01 08:43:16 +01:00
|
|
|
Copyright (C) 2002-2019 John Goerzen & contributors
|
2002-06-19 06:39:00 +02:00
|
|
|
|
2020-11-01 08:43:16 +01:00
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program; if not, write to the Free Software
|
|
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
"""
|
2011-05-11 18:25:13 +02:00
|
|
|
import os
|
|
|
|
import netrc
|
|
|
|
import errno
|
2016-07-08 21:23:27 +02:00
|
|
|
from sys import exc_info
|
|
|
|
from threading import Event
|
2015-01-01 21:41:11 +01:00
|
|
|
from offlineimap import folder, imaputil, imapserver, OfflineImapError
|
2016-07-08 21:23:27 +02:00
|
|
|
from offlineimap.repository.Base import BaseRepository
|
2015-01-01 21:41:11 +01:00
|
|
|
from offlineimap.threadutil import ExitNotifyThread
|
2020-10-25 16:33:52 +01:00
|
|
|
from offlineimap.utils.distro_utils import get_os_sslcertfile, \
|
|
|
|
get_os_sslcertfile_searchpath
|
2015-01-01 21:41:11 +01:00
|
|
|
|
|
|
|
|
2002-06-19 06:39:00 +02:00
|
|
|
class IMAPRepository(BaseRepository):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
IMAP Repository Class, children of BaseRepository
|
|
|
|
"""
|
2003-04-18 04:18:34 +02:00
|
|
|
def __init__(self, reposname, account):
|
2016-09-20 02:17:36 +02:00
|
|
|
self.idlefolders = None
|
2003-04-18 04:18:34 +02:00
|
|
|
BaseRepository.__init__(self, reposname, account)
|
2011-03-03 11:43:23 +01:00
|
|
|
# self.ui is being set by the BaseRepository
|
2011-06-06 13:12:23 +02:00
|
|
|
self._host = None
|
2016-07-28 18:35:33 +02:00
|
|
|
# Must be set before calling imapserver.IMAPServer(self)
|
|
|
|
self.oauth2_request_url = None
|
2011-08-12 08:31:09 +02:00
|
|
|
self.imapserver = imapserver.IMAPServer(self)
|
2002-06-19 07:55:12 +02:00
|
|
|
self.folders = None
|
2016-06-28 23:58:03 +02:00
|
|
|
self.copy_ignore_eval = None
|
2016-07-28 00:42:35 +02:00
|
|
|
# Keep alive.
|
|
|
|
self.kaevent = None
|
|
|
|
self.kathread = None
|
2016-06-28 23:58:03 +02:00
|
|
|
|
2015-01-27 18:56:54 +01:00
|
|
|
# Only set the newmail_hook in an IMAP repository.
|
|
|
|
if self.config.has_option(self.getsection(), 'newmail_hook'):
|
|
|
|
self.newmail_hook = self.localeval.eval(
|
|
|
|
self.getconf('newmail_hook'))
|
|
|
|
|
2012-04-02 23:26:59 +02:00
|
|
|
if self.getconf('sep', None):
|
|
|
|
self.ui.info("The 'sep' setting is being ignored for IMAP "
|
2020-08-29 19:51:25 +02:00
|
|
|
"repository '%s' (it's autodetected)" % self)
|
2003-04-18 04:18:34 +02:00
|
|
|
|
|
|
|
def startkeepalive(self):
|
|
|
|
keepalivetime = self.getkeepalive()
|
2016-07-28 00:42:35 +02:00
|
|
|
if not keepalivetime:
|
|
|
|
return
|
2003-04-18 04:18:34 +02:00
|
|
|
self.kaevent = Event()
|
2016-07-28 00:42:35 +02:00
|
|
|
self.kathread = ExitNotifyThread(target=self.imapserver.keepalive,
|
|
|
|
name="Keep alive " + self.getname(),
|
|
|
|
args=(keepalivetime, self.kaevent))
|
2020-10-31 15:35:54 +01:00
|
|
|
self.kathread.setDaemon(True)
|
2003-04-18 04:18:34 +02:00
|
|
|
self.kathread.start()
|
|
|
|
|
2008-08-03 00:04:32 +02:00
|
|
|
def stopkeepalive(self):
|
2016-07-28 04:06:52 +02:00
|
|
|
if self.kaevent is None:
|
2020-08-29 19:51:25 +02:00
|
|
|
return # Keepalive is not active.
|
2003-04-18 04:18:34 +02:00
|
|
|
|
|
|
|
self.kaevent.set()
|
2016-07-28 04:06:52 +02:00
|
|
|
self.kathread = None
|
|
|
|
self.kaevent = None
|
2003-04-18 04:18:34 +02:00
|
|
|
|
|
|
|
def holdordropconnections(self):
|
|
|
|
if not self.getholdconnectionopen():
|
|
|
|
self.dropconnections()
|
|
|
|
|
|
|
|
def dropconnections(self):
|
|
|
|
self.imapserver.close()
|
|
|
|
|
2016-06-28 23:58:03 +02:00
|
|
|
def get_copy_ignore_UIDs(self, foldername):
|
|
|
|
"""Return a list of UIDs to not copy for this foldername."""
|
|
|
|
|
|
|
|
if self.copy_ignore_eval is None:
|
|
|
|
if self.config.has_option(self.getsection(),
|
2020-08-29 19:51:25 +02:00
|
|
|
'copy_ignore_eval'):
|
2016-06-28 23:58:03 +02:00
|
|
|
self.copy_ignore_eval = self.localeval.eval(
|
2016-07-28 00:42:35 +02:00
|
|
|
self.getconf('copy_ignore_eval'))
|
2016-06-28 23:58:03 +02:00
|
|
|
else:
|
|
|
|
self.copy_ignore_eval = lambda x: None
|
|
|
|
|
|
|
|
return self.copy_ignore_eval(foldername)
|
|
|
|
|
2003-04-18 04:18:34 +02:00
|
|
|
def getholdconnectionopen(self):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
Value of holdconnectionopen or False if it is not set
|
|
|
|
|
|
|
|
Returns: Value of holdconnectionopen or False if it is not set
|
|
|
|
|
|
|
|
"""
|
2011-05-19 21:02:26 +02:00
|
|
|
if self.getidlefolders():
|
2016-09-20 02:17:36 +02:00
|
|
|
return True
|
2016-06-23 03:55:12 +02:00
|
|
|
return self.getconfboolean("holdconnectionopen", False)
|
2003-04-18 04:18:34 +02:00
|
|
|
|
|
|
|
def getkeepalive(self):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
This function returns the keepalive value. If it is not set, then
|
|
|
|
check if the getidlefolders is set. If getidlefolders is set, then
|
|
|
|
returns 29 * 60
|
|
|
|
|
|
|
|
Returns: keepalive value
|
|
|
|
|
|
|
|
"""
|
2011-05-19 21:02:30 +02:00
|
|
|
num = self.getconfint("keepalive", 0)
|
|
|
|
if num == 0 and self.getidlefolders():
|
2020-08-29 19:51:25 +02:00
|
|
|
return 29 * 60
|
2016-09-20 02:17:36 +02:00
|
|
|
return num
|
2002-06-19 07:22:21 +02:00
|
|
|
|
2002-06-19 08:16:19 +02:00
|
|
|
def getsep(self):
|
2011-12-01 09:49:09 +01:00
|
|
|
"""Return the folder separator for the IMAP repository
|
|
|
|
|
|
|
|
This requires that self.imapserver has been initialized with an
|
|
|
|
acquireconnection() or it will still be `None`"""
|
2020-10-31 16:09:34 +01:00
|
|
|
assert self.imapserver.delim is not None, \
|
|
|
|
"'%s' repository called getsep() before the folder separator was " \
|
|
|
|
"queried from the server" % self
|
2002-06-19 08:16:19 +02:00
|
|
|
return self.imapserver.delim
|
|
|
|
|
2003-04-18 04:18:34 +02:00
|
|
|
def gethost(self):
|
2011-06-06 13:12:23 +02:00
|
|
|
"""Return the configured hostname to connect to
|
|
|
|
|
|
|
|
:returns: hostname as string or throws Exception"""
|
2016-07-28 00:42:35 +02:00
|
|
|
if self._host: # Use cached value if possible.
|
2011-06-06 13:12:23 +02:00
|
|
|
return self._host
|
2006-10-17 21:55:03 +02:00
|
|
|
|
2016-07-28 00:42:35 +02:00
|
|
|
# 1) Check for remotehosteval setting.
|
2006-10-17 21:55:03 +02:00
|
|
|
if self.config.has_option(self.getsection(), 'remotehosteval'):
|
2007-07-04 23:00:14 +02:00
|
|
|
host = self.getconf('remotehosteval')
|
2011-06-06 13:12:23 +02:00
|
|
|
try:
|
2021-08-14 01:02:45 +02:00
|
|
|
l_host = self.localeval.eval(host)
|
|
|
|
|
|
|
|
# We need a str host
|
|
|
|
if isinstance(l_host, bytes):
|
|
|
|
return l_host.decode(encoding='utf-8')
|
|
|
|
elif isinstance(l_host, str):
|
|
|
|
return l_host
|
|
|
|
|
|
|
|
# If is not bytes or str, we have a problem
|
|
|
|
raise OfflineImapError("Could not get a right host format for"
|
|
|
|
" repository %s. Type found: %s. "
|
|
|
|
"Please, open a bug." %
|
|
|
|
(self.name, type(l_host)),
|
|
|
|
OfflineImapError.ERROR.FOLDER)
|
|
|
|
|
2020-11-01 12:40:48 +01:00
|
|
|
except Exception as exc:
|
2020-09-03 21:03:58 +02:00
|
|
|
raise OfflineImapError(
|
|
|
|
"remotehosteval option for repository "
|
2020-11-01 12:40:48 +01:00
|
|
|
"'%s' failed:\n%s" % (self, exc),
|
2020-09-03 21:03:58 +02:00
|
|
|
OfflineImapError.ERROR.REPO,
|
2020-11-01 12:45:48 +01:00
|
|
|
exc_info()[2]) from exc
|
2011-06-06 13:12:23 +02:00
|
|
|
if host:
|
|
|
|
self._host = host
|
|
|
|
return self._host
|
2016-07-28 00:42:35 +02:00
|
|
|
# 2) Check for plain remotehost setting.
|
2011-06-06 13:12:23 +02:00
|
|
|
host = self.getconf('remotehost', None)
|
2020-08-30 11:03:57 +02:00
|
|
|
if host is not None:
|
2011-06-06 13:12:23 +02:00
|
|
|
self._host = host
|
|
|
|
return self._host
|
2006-10-17 21:55:03 +02:00
|
|
|
|
2016-07-28 00:42:35 +02:00
|
|
|
# No success.
|
2015-01-14 22:58:25 +01:00
|
|
|
raise OfflineImapError("No remote host for repository "
|
2020-10-31 16:09:34 +01:00
|
|
|
"'%s' specified." % self,
|
|
|
|
OfflineImapError.ERROR.REPO)
|
2003-04-18 04:18:34 +02:00
|
|
|
|
2013-08-03 14:06:44 +02:00
|
|
|
def get_remote_identity(self):
|
2015-01-01 21:41:11 +01:00
|
|
|
"""Remote identity is used for certain SASL mechanisms
|
2013-08-03 14:06:44 +02:00
|
|
|
(currently -- PLAIN) to inform server about the ID
|
2015-01-01 21:41:11 +01:00
|
|
|
we want to authorize as instead of our login name."""
|
2013-08-03 14:06:44 +02:00
|
|
|
|
2016-06-16 19:51:41 +02:00
|
|
|
identity = self.getconf('remote_identity', default=None)
|
2020-08-30 11:03:57 +02:00
|
|
|
if identity is not None:
|
2016-06-16 19:51:41 +02:00
|
|
|
identity = identity.encode('UTF-8')
|
|
|
|
return identity
|
2013-08-03 14:06:44 +02:00
|
|
|
|
2013-08-07 13:43:51 +02:00
|
|
|
def get_auth_mechanisms(self):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
Get the AUTH mechanisms. We have (ranged from the strongest to weakest)
|
|
|
|
these methods: "GSSAPI", "XOAUTH2", "CRAM-MD5", "PLAIN", "LOGIN"
|
|
|
|
|
|
|
|
Returns: The supported AUTH Methods
|
|
|
|
|
|
|
|
"""
|
2014-01-09 21:30:41 +01:00
|
|
|
supported = ["GSSAPI", "XOAUTH2", "CRAM-MD5", "PLAIN", "LOGIN"]
|
2013-08-07 13:43:51 +02:00
|
|
|
# Mechanisms are ranged from the strongest to the
|
|
|
|
# weakest ones.
|
|
|
|
# TODO: we need DIGEST-MD5, it must come before CRAM-MD5
|
2016-07-28 00:42:35 +02:00
|
|
|
# due to the chosen-plaintext resistance.
|
2014-01-09 21:30:41 +01:00
|
|
|
default = ["GSSAPI", "XOAUTH2", "CRAM-MD5", "PLAIN", "LOGIN"]
|
2013-08-07 13:43:51 +02:00
|
|
|
|
|
|
|
mechs = self.getconflist('auth_mechanisms', r',\s*',
|
2020-08-29 19:51:25 +02:00
|
|
|
default)
|
2013-08-07 13:43:51 +02:00
|
|
|
|
2020-11-01 12:40:48 +01:00
|
|
|
for mech in mechs:
|
|
|
|
if mech not in supported:
|
2020-08-30 14:19:34 +02:00
|
|
|
raise OfflineImapError("Repository %s: " % self +
|
2020-10-31 16:09:34 +01:00
|
|
|
"unknown authentication mechanism '%s'"
|
2020-11-01 12:40:48 +01:00
|
|
|
% mech, OfflineImapError.ERROR.REPO)
|
2013-08-07 13:43:51 +02:00
|
|
|
|
|
|
|
self.ui.debug('imap', "Using authentication mechanisms %s" % mechs)
|
|
|
|
return mechs
|
|
|
|
|
2003-04-18 04:18:34 +02:00
|
|
|
def getuser(self):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
Returns the remoteusereval or remoteuser or netrc user value.
|
|
|
|
|
|
|
|
Returns: Returns the remoteusereval or remoteuser or netrc user value.
|
|
|
|
|
|
|
|
"""
|
2006-10-17 21:55:03 +02:00
|
|
|
if self.config.has_option(self.getsection(), 'remoteusereval'):
|
2007-07-04 23:00:14 +02:00
|
|
|
user = self.getconf('remoteusereval')
|
2020-08-30 11:03:57 +02:00
|
|
|
if user is not None:
|
2021-02-08 22:40:32 +01:00
|
|
|
l_user = self.localeval.eval(user)
|
|
|
|
|
|
|
|
# We need a str username
|
|
|
|
if isinstance(l_user, bytes):
|
|
|
|
return l_user.decode(encoding='utf-8')
|
|
|
|
elif isinstance(l_user, str):
|
|
|
|
return l_user
|
|
|
|
|
|
|
|
# If is not bytes or str, we have a problem
|
|
|
|
raise OfflineImapError("Could not get a right username format for"
|
|
|
|
" repository %s. Type found: %s. "
|
|
|
|
"Please, open a bug." %
|
|
|
|
(self.name, type(l_user)),
|
|
|
|
OfflineImapError.ERROR.FOLDER)
|
2006-10-17 21:55:03 +02:00
|
|
|
|
2014-05-31 16:42:25 +02:00
|
|
|
if self.config.has_option(self.getsection(), 'remoteuser'):
|
2016-07-13 01:11:08 +02:00
|
|
|
# Assume the configuration file to be UTF-8 encoded so we must not
|
|
|
|
# encode this string again.
|
2014-05-31 16:42:25 +02:00
|
|
|
user = self.getconf('remoteuser')
|
2020-08-30 11:03:57 +02:00
|
|
|
if user is not None:
|
2016-07-13 01:11:08 +02:00
|
|
|
return user
|
2003-04-18 04:18:34 +02:00
|
|
|
|
2008-03-03 09:21:33 +01:00
|
|
|
try:
|
2010-08-19 20:56:51 +02:00
|
|
|
netrcentry = netrc.netrc().authenticators(self.gethost())
|
2012-02-05 10:14:23 +01:00
|
|
|
except IOError as inst:
|
2008-03-03 09:21:33 +01:00
|
|
|
if inst.errno != errno.ENOENT:
|
|
|
|
raise
|
|
|
|
else:
|
|
|
|
if netrcentry:
|
|
|
|
return netrcentry[0]
|
2003-04-18 04:18:34 +02:00
|
|
|
|
2008-12-02 20:15:44 +01:00
|
|
|
try:
|
2020-10-31 16:14:53 +01:00
|
|
|
netrcentry = netrc.netrc('/etc/netrc')\
|
|
|
|
.authenticators(self.gethost())
|
2012-02-05 10:14:23 +01:00
|
|
|
except IOError as inst:
|
2012-01-08 19:57:03 +01:00
|
|
|
if inst.errno not in (errno.ENOENT, errno.EACCES):
|
2008-12-02 20:15:44 +01:00
|
|
|
raise
|
|
|
|
else:
|
|
|
|
if netrcentry:
|
|
|
|
return netrcentry[0]
|
|
|
|
|
2003-04-18 04:18:34 +02:00
|
|
|
def getport(self):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
Returns remoteporteval value or None if not found.
|
|
|
|
|
|
|
|
Returns: Returns remoteporteval int value or None if not found.
|
|
|
|
|
|
|
|
"""
|
2013-07-10 03:17:01 +02:00
|
|
|
port = None
|
2013-06-20 11:38:45 +02:00
|
|
|
|
2013-07-10 03:17:01 +02:00
|
|
|
if self.config.has_option(self.getsection(), 'remoteporteval'):
|
|
|
|
port = self.getconf('remoteporteval')
|
2020-08-30 11:03:57 +02:00
|
|
|
if port is not None:
|
2013-07-10 03:17:01 +02:00
|
|
|
return self.localeval.eval(port)
|
2013-06-20 11:38:45 +02:00
|
|
|
|
2003-04-18 04:18:34 +02:00
|
|
|
return self.getconfint('remoteport', None)
|
|
|
|
|
2016-02-23 01:45:18 +01:00
|
|
|
def getipv6(self):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
Returns if IPv6 is set. If not set, then return None
|
|
|
|
|
|
|
|
Returns: Boolean flag if IPv6 is set.
|
|
|
|
|
|
|
|
"""
|
2017-02-03 01:11:56 +01:00
|
|
|
return self.getconfboolean('ipv6', None)
|
2016-02-23 01:45:18 +01:00
|
|
|
|
2003-04-18 04:18:34 +02:00
|
|
|
def getssl(self):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
Get the boolean SSL value. Default is True, used if not found.
|
|
|
|
|
|
|
|
Returns: Get the boolean SSL value. Default is True
|
|
|
|
|
|
|
|
"""
|
2016-06-23 03:55:12 +02:00
|
|
|
return self.getconfboolean('ssl', True)
|
2003-04-18 04:18:34 +02:00
|
|
|
|
2008-05-23 21:58:18 +02:00
|
|
|
def getsslclientcert(self):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
Return the SSL client cert (sslclientcert) or None if not found
|
|
|
|
|
|
|
|
Returns: SSL client key (sslclientcert) or None if not found
|
|
|
|
|
|
|
|
"""
|
2014-11-02 09:49:26 +01:00
|
|
|
xforms = [os.path.expanduser, os.path.expandvars, os.path.abspath]
|
|
|
|
return self.getconf_xform('sslclientcert', xforms, None)
|
2008-05-23 21:58:18 +02:00
|
|
|
|
|
|
|
def getsslclientkey(self):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
Return the SSL client key (sslclientkey) or None if not found
|
|
|
|
|
|
|
|
Returns: SSL client key (sslclientkey) or None if not found
|
|
|
|
|
|
|
|
"""
|
2014-11-02 09:49:26 +01:00
|
|
|
xforms = [os.path.expanduser, os.path.expandvars, os.path.abspath]
|
|
|
|
return self.getconf_xform('sslclientkey', xforms, None)
|
2008-05-23 21:58:18 +02:00
|
|
|
|
2010-12-16 13:43:47 +01:00
|
|
|
def getsslcacertfile(self):
|
2015-01-18 08:45:46 +01:00
|
|
|
"""Determines CA bundle.
|
2016-06-08 16:01:58 +02:00
|
|
|
|
2021-10-12 01:27:44 +02:00
|
|
|
Returns path to the CA bundle. It is either explicitely specified
|
|
|
|
or requested via "OS-DEFAULT" value (and we will search known
|
|
|
|
locations for the current OS and distribution).
|
2015-01-18 08:45:46 +01:00
|
|
|
|
2021-10-12 01:27:44 +02:00
|
|
|
If search via "OS-DEFAULT" route yields nothing, we will throw an
|
|
|
|
exception to make our callers distinguish between not specified
|
|
|
|
value and non-existent default CA bundle.
|
2015-01-18 08:45:46 +01:00
|
|
|
|
|
|
|
It is also an error to specify non-existent file via configuration:
|
|
|
|
it will error out later, but, perhaps, with less verbose explanation,
|
|
|
|
so we will also throw an exception. It is consistent with
|
|
|
|
the above behaviour, so any explicitely-requested configuration
|
|
|
|
that doesn't result in an existing file will give an exception.
|
|
|
|
"""
|
2014-11-02 09:49:26 +01:00
|
|
|
xforms = [os.path.expanduser, os.path.expandvars, os.path.abspath]
|
2015-01-18 08:45:46 +01:00
|
|
|
cacertfile = self.getconf_xform('sslcacertfile', xforms, None)
|
2016-06-08 16:01:58 +02:00
|
|
|
# Can't use above cacertfile because of abspath.
|
2021-10-12 01:27:44 +02:00
|
|
|
if self.getconf('sslcacertfile', None) == "OS-DEFAULT":
|
2015-01-18 08:45:46 +01:00
|
|
|
cacertfile = get_os_sslcertfile()
|
2020-08-30 11:03:57 +02:00
|
|
|
if cacertfile is None:
|
2015-01-18 08:45:46 +01:00
|
|
|
searchpath = get_os_sslcertfile_searchpath()
|
|
|
|
if searchpath:
|
2020-08-29 19:51:25 +02:00
|
|
|
reason = "Default CA bundle was requested, " \
|
|
|
|
"but no existing locations available. " \
|
2015-01-18 08:45:46 +01:00
|
|
|
"Tried %s." % (", ".join(searchpath))
|
|
|
|
else:
|
2020-08-29 19:51:25 +02:00
|
|
|
reason = "Default CA bundle was requested, " \
|
|
|
|
"but OfflineIMAP doesn't know any for your " \
|
2015-01-18 08:45:46 +01:00
|
|
|
"current operating system."
|
|
|
|
raise OfflineImapError(reason, OfflineImapError.ERROR.REPO)
|
2011-03-15 11:18:19 +01:00
|
|
|
if cacertfile is None:
|
|
|
|
return None
|
|
|
|
if not os.path.isfile(cacertfile):
|
2020-08-29 19:51:25 +02:00
|
|
|
reason = "CA certfile for repository '%s' couldn't be found. " \
|
2015-01-18 08:45:46 +01:00
|
|
|
"No such file: '%s'" % (self.name, cacertfile)
|
|
|
|
raise OfflineImapError(reason, OfflineImapError.ERROR.REPO)
|
2011-03-15 11:18:19 +01:00
|
|
|
return cacertfile
|
2010-12-16 13:43:47 +01:00
|
|
|
|
2015-08-25 05:32:00 +02:00
|
|
|
def gettlslevel(self):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
Returns the TLS level (tls_level). If not set, returns 'tls_compat'
|
|
|
|
|
|
|
|
Returns: TLS level (tls_level). If not set, returns 'tls_compat'
|
|
|
|
|
|
|
|
"""
|
2015-08-25 05:32:00 +02:00
|
|
|
return self.getconf('tls_level', 'tls_compat')
|
|
|
|
|
2013-07-07 23:18:59 +02:00
|
|
|
def getsslversion(self):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
Returns the SSL version. If not set, returns None.
|
|
|
|
|
|
|
|
Returns: SSL version. If not set, returns None.
|
|
|
|
|
|
|
|
"""
|
2013-07-07 23:18:59 +02:00
|
|
|
return self.getconf('ssl_version', None)
|
|
|
|
|
2016-06-23 03:55:00 +02:00
|
|
|
def getstarttls(self):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
Get the value of starttls. If not set, returns True
|
|
|
|
|
|
|
|
Returns: Value of starttls. If not set, returns True
|
|
|
|
|
|
|
|
"""
|
2016-06-23 03:55:00 +02:00
|
|
|
return self.getconfboolean('starttls', True)
|
|
|
|
|
2011-09-12 09:50:41 +02:00
|
|
|
def get_ssl_fingerprint(self):
|
2015-01-01 21:41:11 +01:00
|
|
|
"""Return array of possible certificate fingerprints.
|
2014-05-06 23:22:29 +02:00
|
|
|
|
|
|
|
Configuration item cert_fingerprint can contain multiple
|
2015-01-01 21:41:11 +01:00
|
|
|
comma-separated fingerprints in hex form."""
|
2014-05-06 23:22:29 +02:00
|
|
|
|
|
|
|
value = self.getconf('cert_fingerprint', "")
|
2020-10-31 16:14:53 +01:00
|
|
|
return [f.strip().lower().replace(":", "")
|
|
|
|
for f in value.split(',') if f]
|
2011-09-12 09:50:41 +02:00
|
|
|
|
2016-07-28 18:35:33 +02:00
|
|
|
def setoauth2_request_url(self, url):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
Set the OAUTH2 URL request.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
url: OAUTH2 URL request
|
|
|
|
|
|
|
|
Returns: None
|
|
|
|
|
|
|
|
"""
|
2016-07-28 18:35:33 +02:00
|
|
|
self.oauth2_request_url = url
|
|
|
|
|
2014-01-09 21:30:41 +01:00
|
|
|
def getoauth2_request_url(self):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
Returns the OAUTH2 URL request from configuration (oauth2_request_url).
|
|
|
|
If it is not found, then returns None
|
|
|
|
|
|
|
|
Returns: OAUTH2 URL request (oauth2_request_url)
|
|
|
|
|
|
|
|
"""
|
2020-08-29 19:51:25 +02:00
|
|
|
if self.oauth2_request_url is not None: # Use cached value if possible.
|
2016-07-28 18:35:33 +02:00
|
|
|
return self.oauth2_request_url
|
2014-01-09 21:30:41 +01:00
|
|
|
|
2016-07-28 18:35:33 +02:00
|
|
|
self.setoauth2_request_url(self.getconf('oauth2_request_url', None))
|
|
|
|
return self.oauth2_request_url
|
2014-01-09 21:30:41 +01:00
|
|
|
|
|
|
|
def getoauth2_refresh_token(self):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
Get the OAUTH2 refresh token from the configuration
|
|
|
|
(oauth2_refresh_token)
|
|
|
|
If the access token is not found, then returns None.
|
|
|
|
|
|
|
|
Returns: OAUTH2 refresh token (oauth2_refresh_token)
|
|
|
|
|
|
|
|
"""
|
2016-07-23 02:49:13 +02:00
|
|
|
refresh_token = self.getconf('oauth2_refresh_token', None)
|
|
|
|
if refresh_token is None:
|
|
|
|
refresh_token = self.localeval.eval(
|
2016-08-14 23:22:35 +02:00
|
|
|
self.getconf('oauth2_refresh_token_eval', "None")
|
|
|
|
)
|
|
|
|
if refresh_token is not None:
|
|
|
|
refresh_token = refresh_token.strip("\n")
|
2016-07-23 02:49:13 +02:00
|
|
|
return refresh_token
|
2014-01-09 21:30:41 +01:00
|
|
|
|
2015-12-28 14:53:53 +01:00
|
|
|
def getoauth2_access_token(self):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
Get the OAUTH2 access token from the configuration (oauth2_access_token)
|
|
|
|
If the access token is not found, then returns None.
|
|
|
|
|
|
|
|
Returns: OAUTH2 access token (oauth2_access_token)
|
|
|
|
|
|
|
|
"""
|
2016-07-23 02:49:13 +02:00
|
|
|
access_token = self.getconf('oauth2_access_token', None)
|
|
|
|
if access_token is None:
|
|
|
|
access_token = self.localeval.eval(
|
2016-08-14 23:22:35 +02:00
|
|
|
self.getconf('oauth2_access_token_eval', "None")
|
|
|
|
)
|
|
|
|
if access_token is not None:
|
|
|
|
access_token = access_token.strip("\n")
|
2016-07-23 02:49:13 +02:00
|
|
|
return access_token
|
2015-12-28 14:53:53 +01:00
|
|
|
|
2014-01-09 21:30:41 +01:00
|
|
|
def getoauth2_client_id(self):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
Get the OAUTH2 client id (oauth2_client_id) from the configuration.
|
|
|
|
If not found, returns None
|
|
|
|
|
|
|
|
Returns: OAUTH2 client id (oauth2_client_id)
|
|
|
|
|
|
|
|
"""
|
2016-07-23 02:49:13 +02:00
|
|
|
client_id = self.getconf('oauth2_client_id', None)
|
|
|
|
if client_id is None:
|
|
|
|
client_id = self.localeval.eval(
|
2016-08-14 23:22:35 +02:00
|
|
|
self.getconf('oauth2_client_id_eval', "None")
|
|
|
|
)
|
|
|
|
if client_id is not None:
|
|
|
|
client_id = client_id.strip("\n")
|
2016-07-23 02:49:13 +02:00
|
|
|
return client_id
|
2014-01-09 21:30:41 +01:00
|
|
|
|
|
|
|
def getoauth2_client_secret(self):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
Get the OAUTH2 client secret (oauth2_client_secret) from the
|
|
|
|
configuration. If it is not found, then returns None.
|
|
|
|
|
|
|
|
Returns: OAUTH2 client secret
|
|
|
|
|
|
|
|
"""
|
2016-07-23 02:49:13 +02:00
|
|
|
client_secret = self.getconf('oauth2_client_secret', None)
|
|
|
|
if client_secret is None:
|
|
|
|
client_secret = self.localeval.eval(
|
2016-08-14 23:22:35 +02:00
|
|
|
self.getconf('oauth2_client_secret_eval', "None")
|
|
|
|
)
|
|
|
|
if client_secret is not None:
|
|
|
|
client_secret = client_secret.strip("\n")
|
2016-07-23 02:49:13 +02:00
|
|
|
return client_secret
|
2014-01-09 21:30:41 +01:00
|
|
|
|
2003-04-18 04:18:34 +02:00
|
|
|
def getpreauthtunnel(self):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
Get the value of preauthtunnel. If not found, then returns None.
|
|
|
|
|
|
|
|
Returns: Returns preauthtunnel value. If not found, returns None.
|
|
|
|
|
|
|
|
"""
|
2003-04-18 04:18:34 +02:00
|
|
|
return self.getconf('preauthtunnel', None)
|
|
|
|
|
2013-05-03 15:56:20 +02:00
|
|
|
def gettransporttunnel(self):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
Get the value of transporttunnel. If not found, then returns None.
|
|
|
|
|
|
|
|
Returns: Returns transporttunnel value. If not found, returns None.
|
|
|
|
|
|
|
|
"""
|
2013-05-03 15:56:20 +02:00
|
|
|
return self.getconf('transporttunnel', None)
|
|
|
|
|
2003-04-18 04:18:34 +02:00
|
|
|
def getreference(self):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
Get the reference value in the configuration. If the value is not found
|
|
|
|
then returns a double quote ("") as string.
|
|
|
|
|
|
|
|
Returns: The reference variable. If not set, then returns '""'
|
|
|
|
|
|
|
|
"""
|
2020-09-01 18:10:03 +02:00
|
|
|
return self.getconf('reference', '""')
|
2003-04-18 04:18:34 +02:00
|
|
|
|
2015-08-29 14:53:20 +02:00
|
|
|
def getdecodefoldernames(self):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
Get the boolean value of decodefoldernames configuration variable,
|
|
|
|
if the value is not found, returns False.
|
|
|
|
|
|
|
|
Returns: Boolean value of decodefoldernames, else False
|
|
|
|
|
|
|
|
"""
|
2016-06-23 03:55:12 +02:00
|
|
|
return self.getconfboolean('decodefoldernames', False)
|
2015-08-29 14:53:20 +02:00
|
|
|
|
2011-05-19 21:02:26 +02:00
|
|
|
def getidlefolders(self):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
Get the list of idlefolders from configuration. If the value is not
|
|
|
|
found, returns an empty list.
|
|
|
|
|
|
|
|
Returns: A list of idle folders
|
|
|
|
|
|
|
|
"""
|
2016-09-20 02:17:36 +02:00
|
|
|
if self.idlefolders is None:
|
|
|
|
self.idlefolders = self.localeval.eval(
|
|
|
|
self.getconf('idlefolders', '[]')
|
|
|
|
)
|
|
|
|
return self.idlefolders
|
2011-05-19 21:02:26 +02:00
|
|
|
|
2003-04-18 04:18:34 +02:00
|
|
|
def getmaxconnections(self):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
Get the maxconnections configuration value from configuration.
|
|
|
|
If the value is not set, returns 1 connection
|
|
|
|
|
|
|
|
Returns: Integer value of maxconnections configuration variable, else 1
|
|
|
|
|
|
|
|
"""
|
2011-05-19 21:02:26 +02:00
|
|
|
num1 = len(self.getidlefolders())
|
|
|
|
num2 = self.getconfint('maxconnections', 1)
|
|
|
|
return max(num1, num2)
|
2003-04-18 04:18:34 +02:00
|
|
|
|
|
|
|
def getexpunge(self):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
Get the expunge configuration value from configuration.
|
|
|
|
If the value is not set in the configuration, then returns True
|
|
|
|
|
|
|
|
Returns: Boolean value of expunge configuration variable
|
|
|
|
|
|
|
|
"""
|
2016-06-23 03:55:12 +02:00
|
|
|
return self.getconfboolean('expunge', True)
|
2003-04-18 04:18:34 +02:00
|
|
|
|
|
|
|
def getpassword(self):
|
2010-12-13 18:45:42 +01:00
|
|
|
"""Return the IMAP password for this repository.
|
2006-10-17 21:55:03 +02:00
|
|
|
|
2010-12-13 18:45:42 +01:00
|
|
|
It tries to get passwords in the following order:
|
|
|
|
|
|
|
|
1. evaluate Repository 'remotepasseval'
|
|
|
|
2. read password from Repository 'remotepass'
|
|
|
|
3. read password from file specified in Repository 'remotepassfile'
|
|
|
|
4. read password from ~/.netrc
|
|
|
|
5. read password from /etc/netrc
|
2006-10-17 21:55:03 +02:00
|
|
|
|
2010-12-13 18:45:42 +01:00
|
|
|
On success we return the password.
|
2015-01-01 21:41:11 +01:00
|
|
|
If all strategies fail we return None."""
|
|
|
|
|
2016-07-28 00:42:35 +02:00
|
|
|
# 1. Evaluate Repository 'remotepasseval'.
|
2010-12-13 18:45:42 +01:00
|
|
|
passwd = self.getconf('remotepasseval', None)
|
2016-07-28 00:42:35 +02:00
|
|
|
if passwd is not None:
|
2020-11-04 22:17:32 +01:00
|
|
|
l_pass = self.localeval.eval(passwd)
|
|
|
|
|
|
|
|
# We need a str password
|
|
|
|
if isinstance(l_pass, bytes):
|
|
|
|
return l_pass.decode(encoding='utf-8')
|
|
|
|
elif isinstance(l_pass, str):
|
|
|
|
return l_pass
|
|
|
|
|
|
|
|
# If is not bytes or str, we have a problem
|
|
|
|
raise OfflineImapError("Could not get a right password format for"
|
|
|
|
" repository %s. Type found: %s. "
|
|
|
|
"Please, open a bug." %
|
|
|
|
(self.name, type(l_pass)),
|
|
|
|
OfflineImapError.ERROR.FOLDER)
|
|
|
|
|
2016-07-28 00:42:35 +02:00
|
|
|
# 2. Read password from Repository 'remotepass'.
|
2003-04-18 04:18:34 +02:00
|
|
|
password = self.getconf('remotepass', None)
|
2016-07-28 00:42:35 +02:00
|
|
|
if password is not None:
|
2016-07-13 01:11:08 +02:00
|
|
|
# Assume the configuration file to be UTF-8 encoded so we must not
|
|
|
|
# encode this string again.
|
|
|
|
return password
|
2016-07-28 00:42:35 +02:00
|
|
|
# 3. Read password from file specified in Repository 'remotepassfile'.
|
2003-04-18 04:18:34 +02:00
|
|
|
passfile = self.getconf('remotepassfile', None)
|
2016-07-28 00:42:35 +02:00
|
|
|
if passfile is not None:
|
2020-11-01 12:40:48 +01:00
|
|
|
file_desc = open(os.path.expanduser(passfile), 'r',
|
|
|
|
encoding='utf-8')
|
|
|
|
password = file_desc.readline().strip()
|
|
|
|
file_desc.close()
|
2021-02-10 22:56:07 +01:00
|
|
|
|
|
|
|
# We need a str password
|
|
|
|
if isinstance(password, bytes):
|
|
|
|
return password.decode(encoding='utf-8')
|
|
|
|
elif isinstance(password, str):
|
|
|
|
return password
|
|
|
|
|
|
|
|
# If is not bytes or str, we have a problem
|
|
|
|
raise OfflineImapError("Could not get a right password format for"
|
|
|
|
" repository %s. Type found: %s. "
|
|
|
|
"Please, open a bug." %
|
|
|
|
(self.name, type(password)),
|
|
|
|
OfflineImapError.ERROR.FOLDER)
|
|
|
|
|
2016-07-28 00:42:35 +02:00
|
|
|
# 4. Read password from ~/.netrc.
|
2008-03-03 09:21:33 +01:00
|
|
|
try:
|
|
|
|
netrcentry = netrc.netrc().authenticators(self.gethost())
|
2012-02-05 10:14:23 +01:00
|
|
|
except IOError as inst:
|
2008-03-03 09:21:33 +01:00
|
|
|
if inst.errno != errno.ENOENT:
|
|
|
|
raise
|
|
|
|
else:
|
|
|
|
if netrcentry:
|
2014-05-31 16:42:25 +02:00
|
|
|
user = self.getuser()
|
2016-07-28 00:42:35 +02:00
|
|
|
if user is None or user == netrcentry[0]:
|
2008-03-03 09:21:33 +01:00
|
|
|
return netrcentry[2]
|
2016-07-28 00:42:35 +02:00
|
|
|
# 5. Read password from /etc/netrc.
|
2008-12-02 20:15:44 +01:00
|
|
|
try:
|
2020-10-31 16:14:53 +01:00
|
|
|
netrcentry = netrc.netrc('/etc/netrc')\
|
|
|
|
.authenticators(self.gethost())
|
2012-02-05 10:14:23 +01:00
|
|
|
except IOError as inst:
|
2012-01-08 19:57:03 +01:00
|
|
|
if inst.errno not in (errno.ENOENT, errno.EACCES):
|
2008-12-02 20:15:44 +01:00
|
|
|
raise
|
|
|
|
else:
|
|
|
|
if netrcentry:
|
2014-05-31 16:42:25 +02:00
|
|
|
user = self.getuser()
|
2016-07-28 00:42:35 +02:00
|
|
|
if user is None or user == netrcentry[0]:
|
2008-12-02 20:15:44 +01:00
|
|
|
return netrcentry[2]
|
2016-07-28 00:42:35 +02:00
|
|
|
# No strategy yielded a password!
|
2003-04-18 04:18:34 +02:00
|
|
|
return None
|
|
|
|
|
2017-10-02 01:26:29 +02:00
|
|
|
def getfolder(self, foldername, decode=True):
|
2015-01-11 13:54:53 +01:00
|
|
|
"""Return instance of OfflineIMAP representative folder."""
|
|
|
|
|
2017-10-02 01:26:29 +02:00
|
|
|
return self.getfoldertype()(self.imapserver, foldername, self, decode)
|
2003-04-18 04:18:34 +02:00
|
|
|
|
|
|
|
def getfoldertype(self):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
This function returns the folder type, in this case
|
|
|
|
folder.IMAP.IMAPFolder
|
|
|
|
|
|
|
|
Returns: folder.IMAP.IMAPFolder
|
|
|
|
|
|
|
|
"""
|
2003-04-18 04:18:34 +02:00
|
|
|
return folder.IMAP.IMAPFolder
|
2002-06-20 04:55:24 +02:00
|
|
|
|
2007-07-05 06:04:14 +02:00
|
|
|
def connect(self):
|
|
|
|
imapobj = self.imapserver.acquireconnection()
|
|
|
|
self.imapserver.releaseconnection(imapobj)
|
|
|
|
|
2007-07-06 18:46:29 +02:00
|
|
|
def forgetfolders(self):
|
|
|
|
self.folders = None
|
|
|
|
|
2002-06-19 07:22:21 +02:00
|
|
|
def getfolders(self):
|
2015-01-11 13:54:53 +01:00
|
|
|
"""Return a list of instances of OfflineIMAP representative folder."""
|
|
|
|
|
2016-07-28 00:42:35 +02:00
|
|
|
if self.folders is not None:
|
2002-06-19 07:55:12 +02:00
|
|
|
return self.folders
|
2002-06-19 07:22:21 +02:00
|
|
|
retval = []
|
2002-07-04 02:15:32 +02:00
|
|
|
imapobj = self.imapserver.acquireconnection()
|
2008-10-01 07:33:57 +02:00
|
|
|
# check whether to list all folders, or subscribed only
|
|
|
|
listfunction = imapobj.list
|
2011-09-15 15:45:09 +02:00
|
|
|
if self.getconfboolean('subscribedonly', False):
|
2008-10-01 07:33:57 +02:00
|
|
|
listfunction = imapobj.lsub
|
2017-01-22 19:20:32 +01:00
|
|
|
|
2002-07-04 02:15:32 +02:00
|
|
|
try:
|
2020-10-31 16:14:53 +01:00
|
|
|
result, listresult = \
|
|
|
|
listfunction(directory=self.imapserver.reference, pattern='"*"')
|
2017-01-22 19:20:32 +01:00
|
|
|
if result != 'OK':
|
|
|
|
raise OfflineImapError("Could not list the folders for"
|
2020-08-29 19:51:25 +02:00
|
|
|
" repository %s. Server responded: %s" %
|
2020-10-31 15:37:50 +01:00
|
|
|
(self.name, str(listresult)),
|
2020-08-29 19:51:25 +02:00
|
|
|
OfflineImapError.ERROR.FOLDER)
|
2002-07-04 02:15:32 +02:00
|
|
|
finally:
|
|
|
|
self.imapserver.releaseconnection(imapobj)
|
2017-01-22 19:20:32 +01:00
|
|
|
|
2020-11-01 12:40:48 +01:00
|
|
|
for fldr in listresult:
|
|
|
|
if fldr is None or (isinstance(fldr, str) and fldr == ''):
|
2002-10-16 07:43:02 +02:00
|
|
|
# Bug in imaplib: empty strings in results from
|
2012-02-05 12:23:49 +01:00
|
|
|
# literals. TODO: still relevant?
|
2002-10-16 07:43:02 +02:00
|
|
|
continue
|
2017-01-22 19:20:32 +01:00
|
|
|
try:
|
2020-11-01 12:40:48 +01:00
|
|
|
flags, delim, name = imaputil.imapsplit(fldr)
|
2017-01-22 19:20:32 +01:00
|
|
|
except ValueError:
|
|
|
|
self.ui.error(
|
2020-11-01 12:40:48 +01:00
|
|
|
"could not correctly parse server response; got: %s" % fldr)
|
2017-01-22 19:20:32 +01:00
|
|
|
raise
|
2002-07-06 02:22:23 +02:00
|
|
|
flaglist = [x.lower() for x in imaputil.flagsplit(flags)]
|
|
|
|
if '\\noselect' in flaglist:
|
2002-06-19 07:22:21 +02:00
|
|
|
continue
|
2017-09-26 15:31:52 +02:00
|
|
|
retval.append(self.getfoldertype()(self.imapserver, name,
|
2011-09-16 10:54:23 +02:00
|
|
|
self))
|
2011-08-14 13:38:13 +02:00
|
|
|
# Add all folderincludes
|
2003-04-18 06:31:25 +02:00
|
|
|
if len(self.folderincludes):
|
|
|
|
imapobj = self.imapserver.acquireconnection()
|
|
|
|
try:
|
|
|
|
for foldername in self.folderincludes:
|
2003-04-18 09:06:04 +02:00
|
|
|
try:
|
2022-03-20 16:36:01 +01:00
|
|
|
# Folder names with spaces requires quotes
|
|
|
|
if ' ' in foldername:
|
|
|
|
foldername = '"' + foldername + '"'
|
2020-10-31 16:09:34 +01:00
|
|
|
imapobj.select(imaputil.utf8_IMAP(foldername),
|
|
|
|
readonly=True)
|
2020-11-01 12:40:48 +01:00
|
|
|
except OfflineImapError as exc:
|
2011-08-12 08:56:56 +02:00
|
|
|
# couldn't select this folderinclude, so ignore folder.
|
2020-11-01 12:40:48 +01:00
|
|
|
if exc.severity > OfflineImapError.ERROR.FOLDER:
|
2011-08-12 08:56:56 +02:00
|
|
|
raise
|
2020-11-01 12:40:48 +01:00
|
|
|
self.ui.error(exc, exc_info()[2],
|
2011-08-12 08:56:56 +02:00
|
|
|
'Invalid folderinclude:')
|
2003-04-18 09:06:04 +02:00
|
|
|
continue
|
2015-01-13 13:59:52 +01:00
|
|
|
retval.append(self.getfoldertype()(
|
2017-10-02 01:26:29 +02:00
|
|
|
self.imapserver, foldername, self, decode=False))
|
2003-04-18 06:31:25 +02:00
|
|
|
finally:
|
|
|
|
self.imapserver.releaseconnection(imapobj)
|
2012-02-06 17:33:50 +01:00
|
|
|
|
|
|
|
if self.foldersort is None:
|
|
|
|
# default sorting by case insensitive transposed name
|
|
|
|
retval.sort(key=lambda x: str.lower(x.getvisiblename()))
|
|
|
|
else:
|
|
|
|
# do foldersort in a python3-compatible way
|
2020-11-01 12:31:18 +01:00
|
|
|
# http://bytes.com/topic/python/answers/ \
|
|
|
|
# 844614-python-3-sorting-comparison-function
|
2012-02-06 17:33:50 +01:00
|
|
|
def cmp2key(mycmp):
|
|
|
|
"""Converts a cmp= function into a key= function
|
|
|
|
We need to keep cmp functions for backward compatibility"""
|
2020-08-29 19:51:25 +02:00
|
|
|
|
2020-08-30 14:19:34 +02:00
|
|
|
class K:
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
Class to compare getvisiblename() between two objects.
|
|
|
|
"""
|
2012-02-06 17:33:50 +01:00
|
|
|
def __init__(self, obj, *args):
|
|
|
|
self.obj = obj
|
2020-08-29 19:51:25 +02:00
|
|
|
|
2012-02-06 17:33:50 +01:00
|
|
|
def __cmp__(self, other):
|
2020-10-31 16:09:34 +01:00
|
|
|
return mycmp(self.obj.getvisiblename(),
|
|
|
|
other.obj.getvisiblename())
|
2020-08-29 19:51:25 +02:00
|
|
|
|
foldersort broken with instance comparison
This patch solves a problem about the comparison of mails used in
foldersort.
When foldersort is used, for example with:
lambda x,y: -cmp(x,y)
The user gets an error:
ERROR: While attempting to sync account 'accountname'
'<' not supported between instances of 'K' and 'K'
Traceback:
File "offlineimap3/offlineimap/accounts.py", line 298, in syncrunner
self.__sync()
File "offlineimap3/offlineimap/accounts.py", line 374, in __sync
remoterepos.getfolders()
File "offlineimap3/offlineimap/repository/IMAP.py", line 725, in getfolders
retval.sort(key=cmp2key(self.foldersort))
The problem is because in Python 3, we must specify all parameters for
total_ordering (see https://docs.python.org/3/library/functools.html in
the functools.total_ordering block):
Given a class defining one or more rich comparison ordering methods,
this class decorator supplies the rest. This simplifies the effort
involved in specifying all of the possible rich comparison operations:
The class must define one of __lt__(), __le__(), __gt__(), or __ge__().
In addition, the class should supply an __eq__() method.
Also, see: https://docs.python.org/3.1/library/stdtypes.html#comparisons
Instances of a class cannot be ordered with respect to other instances
of the same class, or other types of object, unless the class defines
enough of the methods __lt__(), __le__(), __gt__(), and __ge__()
(in general, __lt__() and __eq__() are sufficient, if you want the
conventional meanings of the comparison operators).
This patch implements all methods.
Closes #33
2020-12-30 21:39:17 +01:00
|
|
|
def __lt__(self, other):
|
|
|
|
return self.__cmp__(other) < 0
|
|
|
|
|
|
|
|
def __le__(self, other):
|
|
|
|
return self.__cmp__(other) <= 0
|
|
|
|
|
|
|
|
def __gt__(self, other):
|
|
|
|
return self.__cmp__(other) > 0
|
|
|
|
|
|
|
|
def __ge__(self, other):
|
|
|
|
return self.__cmp__(other) >= 0
|
|
|
|
|
|
|
|
def __eq__(self, other):
|
|
|
|
return self.__cmp__(other) == 0
|
|
|
|
|
|
|
|
def __ne__(self, other):
|
|
|
|
return self.__cmp__(other) != 0
|
|
|
|
|
2012-02-06 17:33:50 +01:00
|
|
|
return K
|
2020-08-29 19:51:25 +02:00
|
|
|
|
2012-02-06 17:33:50 +01:00
|
|
|
retval.sort(key=cmp2key(self.foldersort))
|
|
|
|
|
2002-06-19 07:55:12 +02:00
|
|
|
self.folders = retval
|
2011-08-14 13:38:13 +02:00
|
|
|
return self.folders
|
2003-04-18 04:18:34 +02:00
|
|
|
|
2016-07-08 21:23:27 +02:00
|
|
|
def deletefolder(self, foldername):
|
|
|
|
"""Delete a folder on the IMAP server."""
|
|
|
|
|
2021-08-07 15:46:37 +02:00
|
|
|
# Folder names with spaces requires quotes
|
|
|
|
if ' ' in foldername:
|
|
|
|
foldername = '"' + foldername + '"'
|
|
|
|
|
2017-10-28 19:56:13 +02:00
|
|
|
if self.account.utf_8_support:
|
|
|
|
foldername = imaputil.utf8_IMAP(foldername)
|
2016-07-08 21:23:27 +02:00
|
|
|
imapobj = self.imapserver.acquireconnection()
|
|
|
|
try:
|
|
|
|
result = imapobj.delete(foldername)
|
|
|
|
if result[0] != 'OK':
|
2020-10-31 16:09:34 +01:00
|
|
|
msg = "Folder '%s'[%s] could not be deleted. "\
|
|
|
|
"Server responded: %s" % (foldername, self, str(result))
|
|
|
|
raise OfflineImapError(msg, OfflineImapError.ERROR.FOLDER)
|
2016-07-08 21:23:27 +02:00
|
|
|
finally:
|
|
|
|
self.imapserver.releaseconnection(imapobj)
|
|
|
|
|
2003-04-18 04:18:34 +02:00
|
|
|
def makefolder(self, foldername):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
Create a folder on the IMAP server
|
2011-08-29 14:18:53 +02:00
|
|
|
|
2011-09-30 08:58:08 +02:00
|
|
|
This will not update the list cached in :meth:`getfolders`. You
|
|
|
|
will need to invoke :meth:`forgetfolders` to force new caching
|
|
|
|
when you are done creating folders yourself.
|
|
|
|
|
2020-11-01 12:31:18 +01:00
|
|
|
Args:
|
|
|
|
foldername: Full path of the folder to be created
|
2015-01-01 21:41:11 +01:00
|
|
|
|
2020-11-01 12:31:18 +01:00
|
|
|
Returns: None
|
|
|
|
|
|
|
|
"""
|
2020-08-28 12:49:03 +02:00
|
|
|
if foldername == '':
|
2016-04-09 17:35:15 +02:00
|
|
|
return
|
|
|
|
|
2020-09-02 09:59:32 +02:00
|
|
|
if self.getreference() != '""':
|
2011-09-30 16:53:18 +02:00
|
|
|
foldername = self.getreference() + self.getsep() + foldername
|
2020-08-29 19:51:25 +02:00
|
|
|
if not foldername: # Create top level folder as folder separator.
|
2011-09-30 17:20:11 +02:00
|
|
|
foldername = self.getsep()
|
2019-02-24 03:13:54 +01:00
|
|
|
self.makefolder_single(foldername)
|
|
|
|
return
|
|
|
|
|
|
|
|
parts = foldername.split(self.getsep())
|
2020-10-31 16:14:53 +01:00
|
|
|
folder_paths = [self.getsep().join(parts[:n + 1])
|
|
|
|
for n in range(len(parts))]
|
2019-02-24 03:13:54 +01:00
|
|
|
for folder_path in folder_paths:
|
|
|
|
try:
|
|
|
|
self.makefolder_single(folder_path)
|
2020-11-01 12:40:48 +01:00
|
|
|
except OfflineImapError as exc:
|
|
|
|
if '[ALREADYEXISTS]' not in exc.reason:
|
2019-02-24 03:13:54 +01:00
|
|
|
raise
|
|
|
|
|
|
|
|
def makefolder_single(self, foldername):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
Create a IMAP folder.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
foldername: Folder's name to create
|
|
|
|
|
|
|
|
Returns: None
|
|
|
|
|
|
|
|
"""
|
2011-09-15 15:37:52 +02:00
|
|
|
self.ui.makefolder(self, foldername)
|
|
|
|
if self.account.dryrun:
|
|
|
|
return
|
2003-04-18 04:18:34 +02:00
|
|
|
imapobj = self.imapserver.acquireconnection()
|
|
|
|
try:
|
2021-08-07 15:46:37 +02:00
|
|
|
# Folder names with spaces requires quotes
|
|
|
|
if ' ' in foldername:
|
|
|
|
foldername = '"' + foldername + '"'
|
|
|
|
|
2017-10-02 01:26:29 +02:00
|
|
|
if self.account.utf_8_support:
|
|
|
|
foldername = imaputil.utf8_IMAP(foldername)
|
|
|
|
|
2011-08-29 14:18:53 +02:00
|
|
|
result = imapobj.create(foldername)
|
2003-04-18 04:18:34 +02:00
|
|
|
if result[0] != 'OK':
|
2020-10-31 16:09:34 +01:00
|
|
|
msg = "Folder '%s'[%s] could not be created. "\
|
|
|
|
"Server responded: %s" % (foldername, self, str(result))
|
|
|
|
raise OfflineImapError(msg, OfflineImapError.ERROR.FOLDER)
|
2003-04-18 04:18:34 +02:00
|
|
|
finally:
|
|
|
|
self.imapserver.releaseconnection(imapobj)
|
2013-07-21 21:00:23 +02:00
|
|
|
|
2020-08-29 19:51:25 +02:00
|
|
|
|
2003-04-18 04:18:34 +02:00
|
|
|
class MappedIMAPRepository(IMAPRepository):
|
2020-11-01 12:31:18 +01:00
|
|
|
"""
|
|
|
|
This subclass of IMAPRepository includes only the method
|
|
|
|
getfoldertype modified that returns folder.UIDMaps.MappedIMAPFolder
|
|
|
|
instead of folder.IMAP.IMAPFolder
|
|
|
|
"""
|
2003-04-18 04:18:34 +02:00
|
|
|
def getfoldertype(self):
|
2016-07-08 21:23:27 +02:00
|
|
|
return folder.UIDMaps.MappedIMAPFolder
|