aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG1
-rw-r--r--archivemail.111
-rwxr-xr-xarchivemail.py91
-rw-r--r--archivemail.sgml13
4 files changed, 86 insertions, 30 deletions
diff --git a/CHANGELOG b/CHANGELOG
index f342ee7..e17b4b8 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -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
diff --git a/archivemail.1 b/archivemail.1
index 69b0d48..90c3393 100644
--- a/archivemail.1
+++ b/archivemail.1
@@ -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
diff --git a/archivemail.py b/archivemail.py
index 45a0cdc..afd532a 100755
--- a/archivemail.py
+++ b/archivemail.py
@@ -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 ###############
diff --git a/archivemail.sgml b/archivemail.sgml
index dff716a..93db525 100644
--- a/archivemail.sgml
+++ b/archivemail.sgml
@@ -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>