IMAP: be NAMESPACE-aware; improved probing for guessed mailbox names.

* Automatically add NAMESPACE prefix to the mailbox path if necessary, 
  * Explicitely check for guessed mailbox names with LIST instead of just trying
    to SELECT them. 
  * Updated documentation about NAMESPACE handling.
This commit is contained in:
Nikolaus Schulz 2008-04-08 15:38:51 +00:00
parent 37816fd659
commit 78b4923832
4 changed files with 86 additions and 30 deletions

View File

@ -16,6 +16,7 @@ Version 0.7.3 - UNRELEASED
"Aidant") Closes: #1878940.
* New option --all to archive all messages in a mailbox. Closes: #1764846.
* Fixed a crash when archiving maildirs with --days=0. (Thanks John Goerzen)
* IMAP: automatically add NAMESPACE prefix to a mailbox path if necessary.
Version 0.7.2 - 9 November 2007

View File

@ -314,12 +314,11 @@ username or password.
Note that quoting only a substring will not work, and be aware that your shell
will probably remove unprotected quotes or backslashes.
.PP
\fBIMAP\fR servers supporting subfolders may use any character as a
mailbox path separator, that is, as an equivalent to the slash character on Unix
systems.
If you are archiving an IMAP subfolder, first \fBarchivemail\fR will try
to open a given mailbox name unchanged; if this fails, it will interpret any
slashes in the URL as path separators and try again.
\fBarchivemail\fR tries to be smart when handling mailbox paths.
In particular, it will automatically add an IMAP NAMESPACE
prefix to the mailbox path if necessary; and if you are archiving a subfolder,
you can use the slash as a path separator instead of the IMAP server's
internal representation.
.SH "EXAMPLES"
.PP
.PP

View File

@ -1371,6 +1371,7 @@ def _archive_imap(mailbox_name, final_archive_name):
user_error("imap server %s has login disabled (hint: "
"try ssl/imaps)" % imap_server)
imap_folder = imap_find_mailbox(imap_srv, imap_folder)
roflag = options.dry_run or options.copy_old_mail
# Work around python bug #1277098 (still pending in python << 2.5)
if not roflag:
@ -1379,25 +1380,10 @@ def _archive_imap(mailbox_name, final_archive_name):
vprint("examining imap folder '%s' read-only" % imap_folder)
else:
vprint("selecting imap folder '%s'" % imap_folder)
# First try the given folder name, if this doesn't work, try to fix it.
result, response = imap_srv.select(imap_folder, roflag)
if result != 'OK':
errmsg = "cannot select imap folder; server says '%s'" % response[0]
if not os.path.sep in imap_folder:
unexpected_error(errmsg)
vprint("Selecting '%s' failed; server says: '%s'.\nTrying to "
"fix mailbox path..." % (imap_folder, response[0]))
delim = imap_getdelim(imap_srv)
if not delim:
unexpected_error(errmsg)
imap_folder = imap_folder.replace(os.path.sep, delim)
vprint("Selecting '%s'" % imap_folder)
result, response = imap_srv.select(imap_folder, roflag)
if result == 'OK':
vprint("successfully selected imap folder %s" % imap_folder)
else:
# Report original mailbox path.
unexpected_error(errmsg)
unexpected_error("selecting '%s' failed; server says: '%s'." \
% (imap_folder, response[0]))
# response is e.g. ['1016'] for 1016 messages in folder
total_msg_count = int(response[0])
vprint("folder has %d message(s)" % total_msg_count)
@ -1549,6 +1535,77 @@ def imap_getdelim(imap_server):
return delim
def imap_get_namespace(srv):
"""Return the IMAP namespace prefixes and hierarchy delimiters."""
assert('NAMESPACE' in srv.capabilities)
result, response = srv.namespace()
if result != 'OK':
unexpected_error("Cannot retrieve IMAP namespace; server says: '%s'"
% response[0])
vprint("NAMESPACE response: %s" % repr(response[0]))
# Typical response is e.g.
# ['(("INBOX." ".")) NIL (("#shared." ".")("shared." "."))'] or
# ['(("" ".")) NIL NIL'], see RFC 2342.
# Make a reasonable guess parsing this beast.
ns = re.findall(r'\("([^"]*)" (?:"(.)"|NIL)', response[0])
assert(ns)
return ns
def imap_find_mailbox(srv, mailbox):
"""Find the given mailbox on the IMAP server, correcting an invalid
mailbox path if possible. Return the found mailbox name."""
for curbox in imap_guess_mailboxnames(srv, mailbox):
vprint("Looking for mailbox '%s'..." % curbox)
result, response = srv.list(pattern=curbox)
if result != 'OK':
unexpected_error("LIST command failed; " \
"server says: '%s'" % response[0])
# Say we queried for the mailbox "foo".
# Upon success, response is e.g. ['(\\HasChildren) "." "foo"'].
# Upon failure, response is [None]. Funky imaplib!
if response[0] != None:
break
else:
user_error("Cannot find mailbox '%s' on server." % mailbox)
vprint("Found mailbox '%s'" % curbox)
# Catch \NoSelect here to avoid misleading errors later.
m = re.match(r'\((?P<attrs>[^\)]*)\)', response[0])
if '\\noselect' in m.group('attrs').lower().split():
user_error("Server indicates that mailbox '%s' is not selectable" \
% curbox)
return curbox
def imap_guess_mailboxnames(srv, mailbox):
"""Return a list of possible real IMAP mailbox names in descending order
of preference, compiled by prepending an IMAP namespace prefix if necessary,
and by translating hierarchy delimiters."""
if 'NAMESPACE' in srv.capabilities:
namespace_response = imap_get_namespace(srv)
for nsprefix, hdelim in namespace_response:
if mailbox.startswith(nsprefix):
mailbox = mailbox[len(nsprefix):]
break
else:
# mailbox doesn't start with a namespace prefix;
# choose private namespace, which is the first one.
nsprefix, hdelim = namespace_response[0]
else:
vprint("Server doesn't support NAMESPACE command.")
nsprefix = ""
hdelim = imap_getdelim(srv)
vprint("IMAP namespace prefix: '%s', hierarchy delimiter: '%s'" % \
(nsprefix, hdelim))
boxnames = [nsprefix + mailbox]
if os.path.sep in mailbox:
mailbox = mailbox.replace(os.path.sep, hdelim)
boxnames.append(mailbox) # could have a valid namespace prefix now
if nsprefix:
boxnames.append(nsprefix + mailbox)
return boxnames
############### misc functions ###############

View File

@ -35,7 +35,7 @@
<RefEntry>
<DocInfo><Date>15 March 2008</Date></DocInfo>
<DocInfo><Date>8 April 2008</Date></DocInfo>
<RefMeta>
<RefEntryTitle>archivemail</RefEntryTitle>
@ -479,12 +479,11 @@ Note that quoting only a substring will not work, and be aware that your shell
will probably remove unprotected quotes or backslashes.
</Para>
<Para>
<application/IMAP/ servers supporting subfolders may use any character as a
mailbox path separator, that is, as an equivalent to the slash character on Unix
systems.
If you are archiving an IMAP subfolder, first <command/archivemail/ will try
to open a given mailbox name unchanged; if this fails, it will interpret any
slashes in the <acronym/URL/ as path separators and try again.
<command/archivemail/ tries to be smart when handling mailbox paths.
In particular, it will automatically add an <acronym/IMAP/ <literal/NAMESPACE/
prefix to the mailbox path if necessary; and if you are archiving a subfolder,
you can use the slash as a path separator instead of the <acronym/IMAP/ server's
internal representation.
</Para>
</RefSect3>
</RefSect2>