From 78b4923832a9cb469d4782b5eb1d2bca971254a7 Mon Sep 17 00:00:00 2001 From: Nikolaus Schulz Date: Tue, 8 Apr 2008 15:38:51 +0000 Subject: 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. --- archivemail.py | 91 +++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 74 insertions(+), 17 deletions(-) (limited to 'archivemail.py') 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[^\)]*)\)', 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 ############### -- cgit v1.2.3