diff --git a/mailnews/imap/public/nsIImapFlagAndUidState.idl b/mailnews/imap/public/nsIImapFlagAndUidState.idl index e78c26080b..bf74e4d433 100644 --- a/mailnews/imap/public/nsIImapFlagAndUidState.idl +++ b/mailnews/imap/public/nsIImapFlagAndUidState.idl @@ -70,5 +70,7 @@ interface nsIImapFlagAndUidState : nsISupports */ ACString getCustomAttribute(in unsigned long aUid, in ACString aCustomAttributeName); + void setOtherKeywords(in unsigned short index, in AUTF8String otherKeyword); + AUTF8String getOtherKeywords(in unsigned short index); }; diff --git a/mailnews/imap/src/nsImapFlagAndUidState.cpp b/mailnews/imap/src/nsImapFlagAndUidState.cpp index 7aef4e8d7b..650cef3e73 100644 --- a/mailnews/imap/src/nsImapFlagAndUidState.cpp +++ b/mailnews/imap/src/nsImapFlagAndUidState.cpp @@ -110,6 +110,27 @@ nsImapFlagAndUidState::GetSupportedUserFlags(uint16_t *aFlags) return NS_OK; } +NS_IMETHODIMP +nsImapFlagAndUidState::SetOtherKeywords(uint16_t index, const nsACString &otherKeyword) +{ + if (index == 0) + fOtherKeywords.Clear(); + nsAutoCString flag(otherKeyword); + ToLowerCase(flag); + fOtherKeywords.AppendElement(flag); + return NS_OK; +} + +NS_IMETHODIMP +nsImapFlagAndUidState::GetOtherKeywords(uint16_t index, nsACString &aKeyword) +{ + if (index < fOtherKeywords.Length()) + aKeyword = fOtherKeywords[index]; + else + aKeyword = EmptyCString(); + return NS_OK; +} + // we need to reset our flags, (re-read all) but chances are the memory allocation needed will be // very close to what we were already using diff --git a/mailnews/imap/src/nsImapFlagAndUidState.h b/mailnews/imap/src/nsImapFlagAndUidState.h index 61a64902c9..3b7f1c1188 100644 --- a/mailnews/imap/src/nsImapFlagAndUidState.h +++ b/mailnews/imap/src/nsImapFlagAndUidState.h @@ -51,6 +51,8 @@ private: bool fPartialUIDFetch; uint32_t fNumAdded; bool fStartCapture; + // Keywords (aka, tags) in FLAGS response to SELECT defined by other clients + nsTArray fOtherKeywords; mozilla::Mutex mLock; }; diff --git a/mailnews/imap/src/nsImapMailFolder.cpp b/mailnews/imap/src/nsImapMailFolder.cpp index 0b204b8e94..1b697435c6 100644 --- a/mailnews/imap/src/nsImapMailFolder.cpp +++ b/mailnews/imap/src/nsImapMailFolder.cpp @@ -100,6 +100,7 @@ static NS_DEFINE_CID(kCImapHostSessionList, NS_IIMAPHOSTSESSIONLIST_CID); extern mozilla::LazyLogModule gAutoSyncLog; // defined in nsAutoSyncManager.cpp extern mozilla::LazyLogModule IMAP; // defined in nsImapProtocol.cpp extern mozilla::LazyLogModule IMAP_CS; // For CONDSTORE, defined in nsImapProtocol.cpp +mozilla::LazyLogModule IMAP_KW("IMAP_KW"); // for logging keyword (tag) processing #define MAILNEWS_CUSTOM_HEADERS "mailnews.customHeaders" @@ -4440,7 +4441,7 @@ void nsImapMailFolder::TweakHeaderFlags(nsIImapProtocol* aProtocol, nsIMsgDBHdr if (newFlags) tweakMe->OrFlags(newFlags, &dbHdrFlags); if (!customFlags.IsEmpty()) - (void) HandleCustomFlags(m_curMsgUid, tweakMe, userFlags, customFlags); + (void) HandleCustomFlags(m_curMsgUid, tweakMe, userFlags, customFlags, nullptr); } } } @@ -4751,7 +4752,8 @@ nsImapMailFolder::BeginMessageUpload() nsresult nsImapMailFolder::HandleCustomFlags(nsMsgKey uidOfMessage, nsIMsgDBHdr *dbHdr, uint16_t userFlags, - nsCString &keywords) + nsCString &keywords, + nsIImapFlagAndUidState *flagState) { nsresult rv = GetDatabase(); NS_ENSURE_SUCCESS(rv, rv); @@ -4785,6 +4787,71 @@ nsresult nsImapMailFolder::HandleCustomFlags(nsMsgKey uidOfMessage, if (existingProperty.IsEmpty()) dbHdr->SetStringProperty("junkscoreorigin", "imapflag"); } + + if (flagState && !(userFlags & kImapMsgSupportUserFlag)) + { + nsCString localKeywords; + if (!(userFlags & kImapMsgSupportUserFlag)) + { + dbHdr->GetStringProperty("keywords", getter_Copies(localKeywords)); + MOZ_LOG(IMAP_KW, mozilla::LogLevel::Debug, ("UID=%" PRIu32 ", localKeywords=|%s| rcvdKeyword=|%s|", + uidOfMessage, localKeywords.get(), keywords.get())); + } + nsTArray localKeywordArray; + nsTArray rcvdKeywordArray; + ParseString(localKeywords, ' ', localKeywordArray); + ParseString(keywords, ' ', rcvdKeywordArray); + + nsAutoCString mozLogDefinedKWs; + if (MOZ_LOG_TEST(IMAP_KW, mozilla::LogLevel::Debug)) + mozLogDefinedKWs.AppendLiteral("Defined keywords = |"); + uint32_t i = 0; + while (true) + { + nsAutoCString definedKeyword; + flagState->GetOtherKeywords(i++, definedKeyword); + if (definedKeyword.IsEmpty()) + { + if (MOZ_LOG_TEST(IMAP_KW, mozilla::LogLevel::Debug)) + { + mozLogDefinedKWs.Append('|'); + MOZ_LOG(IMAP_KW, mozilla::LogLevel::Debug, ("%s", mozLogDefinedKWs.get())); + } + break; + } + + if (MOZ_LOG_TEST(IMAP_KW, mozilla::LogLevel::Debug)) + { + mozLogDefinedKWs.Append(definedKeyword.get()); + mozLogDefinedKWs.Append(' '); + } + + bool inLocal = localKeywordArray.Contains(definedKeyword); + bool inRcvd = rcvdKeywordArray.Contains(definedKeyword); + if (inLocal && inRcvd) + rcvdKeywordArray.RemoveElement(definedKeyword); + if (inLocal && !inRcvd) + localKeywordArray.RemoveElement(definedKeyword); + } + // Combine local and rcvd keyword arrays into a single string + // so it can be passed to SetStringProperty(). If element of + // local already in rcvd, avoid duplicates in combined string. + nsAutoCString combinedKeywords; + for (i = 0; i < localKeywordArray.Length(); i++) + { + if (!rcvdKeywordArray.Contains(localKeywordArray[i])) + { + combinedKeywords.Append(localKeywordArray[i]); + combinedKeywords.Append(' '); + } + } + for (i = 0; i < rcvdKeywordArray.Length(); i++) + { + combinedKeywords.Append(rcvdKeywordArray[i]); + combinedKeywords.Append(' '); + } + return dbHdr->SetStringProperty("keywords", combinedKeywords.get()); + } return (userFlags & kImapMsgSupportUserFlag) ? dbHdr->SetStringProperty("keywords", keywords.get()) : NS_OK; } @@ -4830,7 +4897,7 @@ nsresult nsImapMailFolder::SyncFlags(nsIImapFlagAndUidState *flagState) nsCString keywords; if (NS_SUCCEEDED(flagState->GetCustomFlags(uidOfMessage, getter_Copies(keywords)))) - HandleCustomFlags(uidOfMessage, dbHdr, supportedUserFlags, keywords); + HandleCustomFlags(uidOfMessage, dbHdr, supportedUserFlags, keywords, flagState); NotifyMessageFlagsFromHdr(dbHdr, uidOfMessage, flags); } @@ -4935,7 +5002,7 @@ nsImapMailFolder::NotifyMessageFlags(uint32_t aFlags, GetSupportedUserFlags(&supportedUserFlags); NotifyMessageFlagsFromHdr(dbHdr, aMsgKey, aFlags); nsCString keywords(aKeywords); - HandleCustomFlags(aMsgKey, dbHdr, supportedUserFlags, keywords); + HandleCustomFlags(aMsgKey, dbHdr, supportedUserFlags, keywords, nullptr); } } return NS_OK; diff --git a/mailnews/imap/src/nsImapMailFolder.h b/mailnews/imap/src/nsImapMailFolder.h index 3c9bf7c516..45a47a8fc0 100644 --- a/mailnews/imap/src/nsImapMailFolder.h +++ b/mailnews/imap/src/nsImapMailFolder.h @@ -354,7 +354,8 @@ protected: nsresult SyncFlags(nsIImapFlagAndUidState *flagState); nsresult HandleCustomFlags(nsMsgKey uidOfMessage, nsIMsgDBHdr *dbHdr, - uint16_t userFlags, nsCString& keywords); + uint16_t userFlags, nsCString& keywords, + nsIImapFlagAndUidState *flagState); nsresult NotifyMessageFlagsFromHdr(nsIMsgDBHdr *dbHdr, nsMsgKey msgKey, uint32_t flags); diff --git a/mailnews/imap/src/nsImapServerResponseParser.cpp b/mailnews/imap/src/nsImapServerResponseParser.cpp index 91b78714e3..9243066ff0 100644 --- a/mailnews/imap/src/nsImapServerResponseParser.cpp +++ b/mailnews/imap/src/nsImapServerResponseParser.cpp @@ -782,7 +782,7 @@ void nsImapServerResponseParser::mailbox_data() if (fGotPermanentFlags) skip_to_CRLF(); else - parse_folder_flags(); + parse_folder_flags(true); } else if (!PL_strcasecmp(fNextToken, "LIST") || !PL_strcasecmp(fNextToken, "XLIST")) @@ -1850,20 +1850,19 @@ void nsImapServerResponseParser::text() skip_to_CRLF(); } -void nsImapServerResponseParser::parse_folder_flags() +void nsImapServerResponseParser::parse_folder_flags(bool calledForFlags) { uint16_t labelFlags = 0; + bool storeUserFlags = !(fSupportsUserDefinedFlags & kImapMsgSupportUserFlag) && + calledForFlags && fFlagState; + uint16_t numOtherKeywords = 0; do { AdvanceToNextToken(); if (*fNextToken == '(') fNextToken++; - if (!PL_strncasecmp(fNextToken, "$MDNSent", 8)) - fSupportsUserDefinedFlags |= kImapMsgSupportMDNSentFlag; - else if (!PL_strncasecmp(fNextToken, "$Forwarded", 10)) - fSupportsUserDefinedFlags |= kImapMsgSupportForwardedFlag; - else if (!PL_strncasecmp(fNextToken, "\\Seen", 5)) + if (!PL_strncasecmp(fNextToken, "\\Seen", 5)) fSettablePermanentFlags |= kImapMsgSeenFlag; else if (!PL_strncasecmp(fNextToken, "\\Answered", 9)) fSettablePermanentFlags |= kImapMsgAnsweredFlag; @@ -1873,23 +1872,52 @@ void nsImapServerResponseParser::parse_folder_flags() fSettablePermanentFlags |= kImapMsgDeletedFlag; else if (!PL_strncasecmp(fNextToken, "\\Draft", 6)) fSettablePermanentFlags |= kImapMsgDraftFlag; - else if (!PL_strncasecmp(fNextToken, "$Label1", 7)) - labelFlags |= 1; - else if (!PL_strncasecmp(fNextToken, "$Label2", 7)) - labelFlags |= 2; - else if (!PL_strncasecmp(fNextToken, "$Label3", 7)) - labelFlags |= 4; - else if (!PL_strncasecmp(fNextToken, "$Label4", 7)) - labelFlags |= 8; - else if (!PL_strncasecmp(fNextToken, "$Label5", 7)) - labelFlags |= 16; else if (!PL_strncasecmp(fNextToken, "\\*", 2)) { + // User defined and special keywords (tags) can be defined and set for + // mailbox. Should only occur in PERMANENTFLAGS response. fSupportsUserDefinedFlags |= kImapMsgSupportUserFlag; fSupportsUserDefinedFlags |= kImapMsgSupportForwardedFlag; fSupportsUserDefinedFlags |= kImapMsgSupportMDNSentFlag; fSupportsUserDefinedFlags |= kImapMsgLabelFlags; } + else + { + // Treat special and built-in $LabelX's as user defined if a + // save occurs below. + if (!PL_strncasecmp(fNextToken, "$MDNSent", 8)) + fSupportsUserDefinedFlags |= kImapMsgSupportMDNSentFlag; + else if (!PL_strncasecmp(fNextToken, "$Forwarded", 10)) + fSupportsUserDefinedFlags |= kImapMsgSupportForwardedFlag; + else if (!PL_strncasecmp(fNextToken, "$Label1", 7)) + labelFlags |= 1; + else if (!PL_strncasecmp(fNextToken, "$Label2", 7)) + labelFlags |= 2; + else if (!PL_strncasecmp(fNextToken, "$Label3", 7)) + labelFlags |= 4; + else if (!PL_strncasecmp(fNextToken, "$Label4", 7)) + labelFlags |= 8; + else if (!PL_strncasecmp(fNextToken, "$Label5", 7)) + labelFlags |= 16; + + // Save user keywords defined for mailbox, usually by other clients. + // But only do this for FLAGS response, not PERMANENTFLAGS response + // and if '\*' has not appeared in a PERMANENTFLAGS response. + if (storeUserFlags && *fNextToken != '\r') + { + if (*(fNextToken + strlen(fNextToken) - 1) != ')') + { + // Token doesn't end in ')' so save it as is. + fFlagState->SetOtherKeywords(numOtherKeywords++, nsDependentCString(fNextToken)); + } + else + { + // Token ends in ')' so end of list. Change ')' to null and save. + fFlagState->SetOtherKeywords(numOtherKeywords++, + nsDependentCSubstring(fNextToken, strlen(fNextToken) - 1)); + } + } + } } while (!fAtEndOfLine && ContinueParse()); if (labelFlags == 31) @@ -1947,7 +1975,7 @@ void nsImapServerResponseParser::resp_text_code() uint32_t saveSettableFlags = fSettablePermanentFlags; fSupportsUserDefinedFlags = 0; // assume no unless told fSettablePermanentFlags = 0; // assume none, unless told otherwise. - parse_folder_flags(); + parse_folder_flags(false); // if the server tells us there are no permanent flags, we're // just going to pretend that the FLAGS response flags, if any, are // permanent in case the server is broken. This will allow us diff --git a/mailnews/imap/src/nsImapServerResponseParser.h b/mailnews/imap/src/nsImapServerResponseParser.h index af8dc48ff2..9072114d97 100644 --- a/mailnews/imap/src/nsImapServerResponseParser.h +++ b/mailnews/imap/src/nsImapServerResponseParser.h @@ -137,7 +137,7 @@ protected: virtual void resp_cond_state(bool isTagged); virtual void text_mime2(); virtual void text(); - virtual void parse_folder_flags(); + virtual void parse_folder_flags(bool calledForFlags); virtual void enable_data(); virtual void language_data(); virtual void authChallengeResponse_data();