diff options
Diffstat (limited to '')
-rw-r--r-- | comm/mailnews/search/src/nsMsgSearchAdapter.cpp | 1109 |
1 files changed, 1109 insertions, 0 deletions
diff --git a/comm/mailnews/search/src/nsMsgSearchAdapter.cpp b/comm/mailnews/search/src/nsMsgSearchAdapter.cpp new file mode 100644 index 0000000000..0a2fa5d2ee --- /dev/null +++ b/comm/mailnews/search/src/nsMsgSearchAdapter.cpp @@ -0,0 +1,1109 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "msgCore.h" +#include "nsTextFormatter.h" +#include "nsMsgSearchCore.h" +#include "nsMsgSearchAdapter.h" +#include "nsMsgSearchScopeTerm.h" +#include "nsMsgI18N.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIPrefLocalizedString.h" +#include "nsMsgSearchTerm.h" +#include "nsMsgSearchBoolExpression.h" +#include "nsIIOService.h" +#include "nsNetCID.h" +#include "prprf.h" +#include "mozilla/UniquePtr.h" +#include "prmem.h" +#include "MailNewsTypes.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsMemory.h" +#include "nsMsgMessageFlags.h" +#include "mozilla/Attributes.h" +#include "nsIMsgNewsFolder.h" + +// This stuff lives in the base class because the IMAP search syntax +// is used by the Dredd SEARCH command as well as IMAP itself + +// km - the NOT and HEADER strings are not encoded with a trailing +// <space> because they always precede a mnemonic that has a +// preceding <space> and double <space> characters cause some +// imap servers to return an error. +const char* nsMsgSearchAdapter::m_kImapBefore = " SENTBEFORE "; +const char* nsMsgSearchAdapter::m_kImapBody = " BODY "; +const char* nsMsgSearchAdapter::m_kImapCC = " CC "; +const char* nsMsgSearchAdapter::m_kImapFrom = " FROM "; +const char* nsMsgSearchAdapter::m_kImapNot = " NOT"; +const char* nsMsgSearchAdapter::m_kImapUnDeleted = " UNDELETED"; +const char* nsMsgSearchAdapter::m_kImapOr = " OR"; +const char* nsMsgSearchAdapter::m_kImapSince = " SENTSINCE "; +const char* nsMsgSearchAdapter::m_kImapSubject = " SUBJECT "; +const char* nsMsgSearchAdapter::m_kImapTo = " TO "; +const char* nsMsgSearchAdapter::m_kImapHeader = " HEADER"; +const char* nsMsgSearchAdapter::m_kImapAnyText = " TEXT "; +const char* nsMsgSearchAdapter::m_kImapKeyword = " KEYWORD "; +const char* nsMsgSearchAdapter::m_kNntpKeywords = " KEYWORDS "; // ggrrrr... +const char* nsMsgSearchAdapter::m_kImapSentOn = " SENTON "; +const char* nsMsgSearchAdapter::m_kImapSeen = " SEEN "; +const char* nsMsgSearchAdapter::m_kImapAnswered = " ANSWERED "; +const char* nsMsgSearchAdapter::m_kImapNotSeen = " UNSEEN "; +const char* nsMsgSearchAdapter::m_kImapNotAnswered = " UNANSWERED "; +const char* nsMsgSearchAdapter::m_kImapCharset = " CHARSET "; +const char* nsMsgSearchAdapter::m_kImapSizeSmaller = " SMALLER "; +const char* nsMsgSearchAdapter::m_kImapSizeLarger = " LARGER "; +const char* nsMsgSearchAdapter::m_kImapNew = " NEW "; +const char* nsMsgSearchAdapter::m_kImapNotNew = " OLD SEEN "; +const char* nsMsgSearchAdapter::m_kImapFlagged = " FLAGGED "; +const char* nsMsgSearchAdapter::m_kImapNotFlagged = " UNFLAGGED "; + +#define PREF_CUSTOM_HEADERS "mailnews.customHeaders" + +NS_IMETHODIMP nsMsgSearchAdapter::FindTargetFolder(const nsMsgResultElement*, + nsIMsgFolder**) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgSearchAdapter::ModifyResultElement(nsMsgResultElement*, + nsMsgSearchValue*) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgSearchAdapter::OpenResultElement(nsMsgResultElement*) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMPL_ISUPPORTS(nsMsgSearchAdapter, nsIMsgSearchAdapter) + +nsMsgSearchAdapter::nsMsgSearchAdapter( + nsIMsgSearchScopeTerm* scope, + nsTArray<RefPtr<nsIMsgSearchTerm>> const& searchTerms) + : m_scope(scope), m_searchTerms(searchTerms.Clone()) {} + +nsMsgSearchAdapter::~nsMsgSearchAdapter() {} + +NS_IMETHODIMP nsMsgSearchAdapter::ClearScope() { + if (m_scope) { + m_scope->CloseInputStream(); + m_scope = nullptr; + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchAdapter::ValidateTerms() { + // all this used to do is check if the object had been deleted - we can skip + // that. + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchAdapter::Abort() { return NS_ERROR_NOT_IMPLEMENTED; } +NS_IMETHODIMP nsMsgSearchAdapter::Search(bool* aDone) { return NS_OK; } + +NS_IMETHODIMP nsMsgSearchAdapter::SendUrl() { return NS_OK; } + +/* void CurrentUrlDone (in nsresult exitCode); */ +NS_IMETHODIMP nsMsgSearchAdapter::CurrentUrlDone(nsresult exitCode) { + // base implementation doesn't need to do anything. + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchAdapter::GetEncoding(char** encoding) { return NS_OK; } + +NS_IMETHODIMP nsMsgSearchAdapter::AddResultElement(nsIMsgDBHdr* pHeaders) { + NS_ASSERTION(false, "shouldn't call this base class impl"); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsMsgSearchAdapter::AddHit(nsMsgKey key) { + NS_ASSERTION(false, "shouldn't call this base class impl"); + return NS_ERROR_FAILURE; +} + +char* nsMsgSearchAdapter::GetImapCharsetParam(const char16_t* destCharset) { + char* result = nullptr; + + // Specify a character set unless we happen to be US-ASCII. + if (NS_strcmp(destCharset, u"us-ascii")) + result = PR_smprintf("%s%s", nsMsgSearchAdapter::m_kImapCharset, + NS_ConvertUTF16toUTF8(destCharset).get()); + + return result; +} + +/* + 09/21/2000 - taka@netscape.com + This method is bogus. Escape must be done against char * not char16_t * + should be rewritten later. + for now, just duplicate the string. +*/ +char16_t* nsMsgSearchAdapter::EscapeSearchUrl(const char16_t* nntpCommand) { + return nntpCommand ? NS_xstrdup(nntpCommand) : nullptr; +} + +/* + 09/21/2000 - taka@netscape.com + This method is bogus. Escape must be done against char * not char16_t * + should be rewritten later. + for now, just duplicate the string. +*/ +char16_t* nsMsgSearchAdapter::EscapeImapSearchProtocol( + const char16_t* imapCommand) { + return imapCommand ? NS_xstrdup(imapCommand) : nullptr; +} + +/* + 09/21/2000 - taka@netscape.com + This method is bogus. Escape must be done against char * not char16_t * + should be rewritten later. + for now, just duplicate the string. +*/ +char16_t* nsMsgSearchAdapter::EscapeQuoteImapSearchProtocol( + const char16_t* imapCommand) { + return imapCommand ? NS_xstrdup(imapCommand) : nullptr; +} + +char* nsMsgSearchAdapter::UnEscapeSearchUrl(const char* commandSpecificData) { + char* result = (char*)PR_Malloc(strlen(commandSpecificData) + 1); + if (result) { + char* resultPtr = result; + while (1) { + char ch = *commandSpecificData++; + if (!ch) break; + if (ch == '\\') { + char scratchBuf[3]; + scratchBuf[0] = (char)*commandSpecificData++; + scratchBuf[1] = (char)*commandSpecificData++; + scratchBuf[2] = '\0'; + unsigned int accum = 0; + sscanf(scratchBuf, "%X", &accum); + *resultPtr++ = (char)accum; + } else + *resultPtr++ = ch; + } + *resultPtr = '\0'; + } + return result; +} + +nsresult nsMsgSearchAdapter::GetSearchCharsets(nsAString& srcCharset, + nsAString& dstCharset) { + nsresult rv; + bool forceAsciiSearch = false; + + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + prefs->GetBoolPref("mailnews.force_ascii_search", &forceAsciiSearch); + } + + srcCharset = m_defaultCharset; + dstCharset.Assign(srcCharset); + + if (m_scope) { + nsCOMPtr<nsIMsgFolder> folder; + rv = m_scope->GetFolder(getter_AddRefs(folder)); + if (NS_SUCCEEDED(rv) && folder) { + nsCOMPtr<nsIMsgNewsFolder> newsfolder(do_QueryInterface(folder)); + if (newsfolder) { + nsCString folderCharset; + rv = newsfolder->GetCharset(folderCharset); + if (NS_SUCCEEDED(rv)) + dstCharset.Assign(NS_ConvertASCIItoUTF16(folderCharset)); + } + } + } + + if (forceAsciiSearch) { + // Special cases to use in order to force US-ASCII searching with Latin1 + // or MacRoman text. Eurgh. This only has to happen because IMAP + // and Dredd servers currently (4/23/97) only support US-ASCII. + // + // If the dest csid is ISO Latin 1 or MacRoman, attempt to convert the + // source text to US-ASCII. (Not for now.) + // if ((dst_csid == CS_LATIN1) || (dst_csid == CS_MAC_ROMAN)) + dstCharset.AssignLiteral("us-ascii"); + } + + return NS_OK; +} + +nsresult nsMsgSearchAdapter::EncodeImapTerm(nsIMsgSearchTerm* term, + bool reallyDredd, + const char16_t* srcCharset, + const char16_t* destCharset, + char** ppOutTerm) { + NS_ENSURE_ARG_POINTER(term); + NS_ENSURE_ARG_POINTER(ppOutTerm); + + nsresult err = NS_OK; + bool useNot = false; + bool useQuotes = false; + bool ignoreValue = false; + nsAutoCString arbitraryHeader; + const char* whichMnemonic = nullptr; + const char* orHeaderMnemonic = nullptr; + + *ppOutTerm = nullptr; + + nsCOMPtr<nsIMsgSearchValue> searchValue; + nsresult rv = term->GetValue(getter_AddRefs(searchValue)); + + NS_ENSURE_SUCCESS(rv, rv); + + nsMsgSearchOpValue op; + term->GetOp(&op); + + if (op == nsMsgSearchOp::DoesntContain || op == nsMsgSearchOp::Isnt) + useNot = true; + + nsMsgSearchAttribValue attrib; + term->GetAttrib(&attrib); + + switch (attrib) { + case nsMsgSearchAttrib::ToOrCC: + orHeaderMnemonic = m_kImapCC; + // fall through to case nsMsgSearchAttrib::To: + [[fallthrough]]; + case nsMsgSearchAttrib::To: + whichMnemonic = m_kImapTo; + break; + case nsMsgSearchAttrib::CC: + whichMnemonic = m_kImapCC; + break; + case nsMsgSearchAttrib::Sender: + whichMnemonic = m_kImapFrom; + break; + case nsMsgSearchAttrib::Subject: + whichMnemonic = m_kImapSubject; + break; + case nsMsgSearchAttrib::Body: + whichMnemonic = m_kImapBody; + break; + case nsMsgSearchAttrib::AgeInDays: // added for searching online for age in + // days... + // for AgeInDays, we are actually going to perform a search by date, so + // convert the operations for age to the IMAP mnemonics that we would use + // for date! + { + // If we have a future date, the > and < are reversed. + // e.g. ageInDays > 2 means more than 2 days old ("date before X") + // whereas + // ageInDays > -2 should be more than 2 days in the future ("date + // after X") + int32_t ageInDays; + searchValue->GetAge(&ageInDays); + bool dateInFuture = (ageInDays < 0); + switch (op) { + case nsMsgSearchOp::IsGreaterThan: + whichMnemonic = (!dateInFuture) ? m_kImapBefore : m_kImapSince; + break; + case nsMsgSearchOp::IsLessThan: + whichMnemonic = (!dateInFuture) ? m_kImapSince : m_kImapBefore; + break; + case nsMsgSearchOp::Is: + whichMnemonic = m_kImapSentOn; + break; + default: + NS_ASSERTION(false, "invalid search operator"); + return NS_ERROR_INVALID_ARG; + } + } + break; + case nsMsgSearchAttrib::Size: + switch (op) { + case nsMsgSearchOp::IsGreaterThan: + whichMnemonic = m_kImapSizeLarger; + break; + case nsMsgSearchOp::IsLessThan: + whichMnemonic = m_kImapSizeSmaller; + break; + default: + NS_ASSERTION(false, "invalid search operator"); + return NS_ERROR_INVALID_ARG; + } + break; + case nsMsgSearchAttrib::Date: + switch (op) { + case nsMsgSearchOp::IsBefore: + whichMnemonic = m_kImapBefore; + break; + case nsMsgSearchOp::IsAfter: + whichMnemonic = m_kImapSince; + break; + case nsMsgSearchOp::Isnt: /* we've already added the "Not" so just + process it like it was a date is search */ + case nsMsgSearchOp::Is: + whichMnemonic = m_kImapSentOn; + break; + default: + NS_ASSERTION(false, "invalid search operator"); + return NS_ERROR_INVALID_ARG; + } + break; + case nsMsgSearchAttrib::AnyText: + whichMnemonic = m_kImapAnyText; + break; + case nsMsgSearchAttrib::Keywords: + whichMnemonic = m_kImapKeyword; + break; + case nsMsgSearchAttrib::MsgStatus: + useNot = false; // bizarrely, NOT SEEN is wrong, but UNSEEN is right. + ignoreValue = true; // the mnemonic is all we need + uint32_t status; + searchValue->GetStatus(&status); + + switch (status) { + case nsMsgMessageFlags::Read: + whichMnemonic = + op == nsMsgSearchOp::Is ? m_kImapSeen : m_kImapNotSeen; + break; + case nsMsgMessageFlags::Replied: + whichMnemonic = + op == nsMsgSearchOp::Is ? m_kImapAnswered : m_kImapNotAnswered; + break; + case nsMsgMessageFlags::New: + whichMnemonic = op == nsMsgSearchOp::Is ? m_kImapNew : m_kImapNotNew; + break; + case nsMsgMessageFlags::Marked: + whichMnemonic = + op == nsMsgSearchOp::Is ? m_kImapFlagged : m_kImapNotFlagged; + break; + default: + NS_ASSERTION(false, "invalid search operator"); + return NS_ERROR_INVALID_ARG; + } + break; + default: + if (attrib > nsMsgSearchAttrib::OtherHeader && + attrib < nsMsgSearchAttrib::kNumMsgSearchAttributes) { + nsCString arbitraryHeaderTerm; + term->GetArbitraryHeader(arbitraryHeaderTerm); + if (!arbitraryHeaderTerm.IsEmpty()) { + arbitraryHeader.AssignLiteral(" \""); + arbitraryHeader.Append(arbitraryHeaderTerm); + arbitraryHeader.AppendLiteral("\" "); + whichMnemonic = arbitraryHeader.get(); + } else + return NS_ERROR_FAILURE; + } else { + NS_ASSERTION(false, "invalid search operator"); + return NS_ERROR_INVALID_ARG; + } + } + + char* value = nullptr; + char dateBuf[100]; + dateBuf[0] = '\0'; + + bool valueWasAllocated = false; + if (attrib == nsMsgSearchAttrib::Date) { + // note that there used to be code here that encoded an RFC822 date for imap + // searches. The IMAP RFC 2060 is misleading to the point that it looks like + // it requires an RFC822 date but really it expects dd-mmm-yyyy, like dredd, + // and refers to the RFC822 date only in that the dd-mmm-yyyy date will + // match the RFC822 date within the message. + + PRTime adjustedDate; + searchValue->GetDate(&adjustedDate); + if (whichMnemonic == m_kImapSince) { + // it looks like the IMAP server searches on Since includes the date in + // question... our UI presents Is, IsGreater and IsLessThan. For the + // IsGreater case (m_kImapSince) we need to adjust the date so we get + // greater than and not greater than or equal to which is what the IMAP + // server wants to search on won't work on Mac. + adjustedDate += PR_USEC_PER_DAY; + } + + PRExplodedTime exploded; + PR_ExplodeTime(adjustedDate, PR_LocalTimeParameters, &exploded); + PR_FormatTimeUSEnglish(dateBuf, sizeof(dateBuf), "%d-%b-%Y", &exploded); + // strftime (dateBuf, sizeof(dateBuf), "%d-%b-%Y", localtime (/* + // &term->m_value.u.date */ &adjustedDate)); + value = dateBuf; + } else { + if (attrib == nsMsgSearchAttrib::AgeInDays) { + // okay, take the current date, subtract off the age in days, then do an + // appropriate Date search on the resulting day. + int32_t ageInDays; + + searchValue->GetAge(&ageInDays); + + PRTime now = PR_Now(); + PRTime matchDay = now - ageInDays * PR_USEC_PER_DAY; + + PRExplodedTime exploded; + PR_ExplodeTime(matchDay, PR_LocalTimeParameters, &exploded); + PR_FormatTimeUSEnglish(dateBuf, sizeof(dateBuf), "%d-%b-%Y", &exploded); + // strftime (dateBuf, sizeof(dateBuf), "%d-%b-%Y", localtime + // (&matchDay)); + value = dateBuf; + } else if (attrib == nsMsgSearchAttrib::Size) { + uint32_t sizeValue; + nsAutoCString searchTermValue; + searchValue->GetSize(&sizeValue); + + // Multiply by 1024 to get into kb resolution + sizeValue *= 1024; + + // Ensure that greater than is really greater than + // in kb resolution. + if (op == nsMsgSearchOp::IsGreaterThan) sizeValue += 1024; + + searchTermValue.AppendInt(sizeValue); + + value = ToNewCString(searchTermValue); + valueWasAllocated = true; + } else + + if (IS_STRING_ATTRIBUTE(attrib)) { + char16_t* + convertedValue; // = reallyDredd ? MSG_EscapeSearchUrl + // (term->m_value.u.string) : + // msg_EscapeImapSearchProtocol(term->m_value.u.string); + nsString searchTermValue; + searchValue->GetStr(searchTermValue); + // Ugly switch for Korean mail/news charsets. + // We want to do this here because here is where + // we know what charset we want to use. +#ifdef DOING_CHARSET + if (reallyDredd) + dest_csid = INTL_DefaultNewsCharSetID(dest_csid); + else + dest_csid = INTL_DefaultMailCharSetID(dest_csid); +#endif + + // do all sorts of crazy escaping + convertedValue = reallyDredd + ? EscapeSearchUrl(searchTermValue.get()) + : EscapeImapSearchProtocol(searchTermValue.get()); + useQuotes = + ((!reallyDredd || + (nsDependentString(convertedValue).FindChar(char16_t(' ')) != + -1)) && + (attrib != nsMsgSearchAttrib::Keywords)); + // now convert to char* and escape quoted_specials + nsAutoCString valueStr; + nsresult rv = nsMsgI18NConvertFromUnicode( + NS_LossyConvertUTF16toASCII(destCharset), + nsDependentString(convertedValue), valueStr); + if (NS_SUCCEEDED(rv)) { + const char* vptr = valueStr.get(); + // max escaped length is one extra character for every character in the + // cmd. + mozilla::UniquePtr<char[]> newValue = + mozilla::MakeUnique<char[]>(2 * strlen(vptr) + 1); + if (newValue) { + char* p = newValue.get(); + while (1) { + char ch = *vptr++; + if (!ch) break; + if ((useQuotes ? ch == '"' : 0) || ch == '\\') *p++ = '\\'; + *p++ = ch; + } + *p = '\0'; + value = strdup(newValue.get()); // realloc down to smaller size + } + } else + value = strdup(""); + free(convertedValue); + valueWasAllocated = true; + } + } + + // this should be rewritten to use nsCString + int subLen = (value ? strlen(value) : 0) + (useNot ? strlen(m_kImapNot) : 0) + + strlen(m_kImapHeader); + int len = + strlen(whichMnemonic) + subLen + (useQuotes ? 2 : 0) + + (orHeaderMnemonic + ? (subLen + strlen(m_kImapOr) + strlen(orHeaderMnemonic) + 2 /*""*/) + : 0) + + 10; // add slough for imap string literals + char* encoding = new char[len]; + if (encoding) { + encoding[0] = '\0'; + // Remember: if ToOrCC and useNot then the expression becomes NOT To AND Not + // CC as opposed to (NOT TO) || (NOT CC) + if (orHeaderMnemonic && !useNot) PL_strcat(encoding, m_kImapOr); + if (useNot) PL_strcat(encoding, m_kImapNot); + if (!arbitraryHeader.IsEmpty()) PL_strcat(encoding, m_kImapHeader); + PL_strcat(encoding, whichMnemonic); + if (!ignoreValue) + err = EncodeImapValue(encoding, value, useQuotes, reallyDredd); + + if (orHeaderMnemonic) { + if (useNot) PL_strcat(encoding, m_kImapNot); + + PL_strcat(encoding, m_kImapHeader); + + PL_strcat(encoding, orHeaderMnemonic); + if (!ignoreValue) + err = EncodeImapValue(encoding, value, useQuotes, reallyDredd); + } + + // kmcentee, don't let the encoding end with whitespace, + // this throws off later url STRCMP + if (*encoding && *(encoding + strlen(encoding) - 1) == ' ') + *(encoding + strlen(encoding) - 1) = '\0'; + } + + if (value && valueWasAllocated) free(value); + + *ppOutTerm = encoding; + + return err; +} + +nsresult nsMsgSearchAdapter::EncodeImapValue(char* encoding, const char* value, + bool useQuotes, bool reallyDredd) { + // By NNTP RFC, SEARCH HEADER SUBJECT "" is legal and means 'find messages + // without a subject header' + if (!reallyDredd) { + // By IMAP RFC, SEARCH HEADER SUBJECT "" is illegal and will generate an + // error from the server + if (!value || !value[0]) return NS_ERROR_NULL_POINTER; + } + + if (!NS_IsAscii(value)) { + nsAutoCString lengthStr; + PL_strcat(encoding, "{"); + lengthStr.AppendInt((int32_t)strlen(value)); + PL_strcat(encoding, lengthStr.get()); + PL_strcat(encoding, "}" CRLF); + PL_strcat(encoding, value); + return NS_OK; + } + if (useQuotes) PL_strcat(encoding, "\""); + PL_strcat(encoding, value); + if (useQuotes) PL_strcat(encoding, "\""); + + return NS_OK; +} + +nsresult nsMsgSearchAdapter::EncodeImap( + char** ppOutEncoding, nsTArray<RefPtr<nsIMsgSearchTerm>> const& searchTerms, + const char16_t* srcCharset, const char16_t* destCharset, bool reallyDredd) { + // i've left the old code (before using CBoolExpression for debugging purposes + // to make sure that the new code generates the same encoding string as the + // old code..... + + nsresult err = NS_OK; + *ppOutEncoding = nullptr; + + // create our expression + nsMsgSearchBoolExpression* expression = new nsMsgSearchBoolExpression(); + if (!expression) return NS_ERROR_OUT_OF_MEMORY; + + for (nsIMsgSearchTerm* pTerm : searchTerms) { + bool matchAll; + pTerm->GetMatchAll(&matchAll); + if (matchAll) continue; + char* termEncoding; + err = EncodeImapTerm(pTerm, reallyDredd, srcCharset, destCharset, + &termEncoding); + if (NS_SUCCEEDED(err) && nullptr != termEncoding) { + expression = nsMsgSearchBoolExpression::AddSearchTerm(expression, pTerm, + termEncoding); + delete[] termEncoding; + } else { + break; + } + } + + if (NS_SUCCEEDED(err)) { + // Catenate the intermediate encodings together into a big string + nsAutoCString encodingBuff; + + if (!reallyDredd) encodingBuff.Append(m_kImapUnDeleted); + + expression->GenerateEncodeStr(&encodingBuff); + *ppOutEncoding = ToNewCString(encodingBuff); + } + + delete expression; + + return err; +} + +char* nsMsgSearchAdapter::TransformSpacesToStars( + const char* spaceString, msg_TransformType transformType) { + char* starString; + + if (transformType == kOverwrite) { + if ((starString = strdup(spaceString)) != nullptr) { + char* star = starString; + while ((star = PL_strchr(star, ' ')) != nullptr) *star = '*'; + } + } else { + int i, count; + + for (i = 0, count = 0; spaceString[i];) { + if (spaceString[i++] == ' ') { + count++; + while (spaceString[i] && spaceString[i] == ' ') i++; + } + } + + if (transformType == kSurround) count *= 2; + + if (count > 0) { + if ((starString = (char*)PR_Malloc(i + count + 1)) != nullptr) { + int j; + + for (i = 0, j = 0; spaceString[i];) { + if (spaceString[i] == ' ') { + starString[j++] = '*'; + starString[j++] = ' '; + if (transformType == kSurround) starString[j++] = '*'; + + i++; + while (spaceString[i] && spaceString[i] == ' ') i++; + } else + starString[j++] = spaceString[i++]; + } + starString[j] = 0; + } + } else + starString = strdup(spaceString); + } + + return starString; +} + +//----------------------------------------------------------------------------- +//------------------- Validity checking for menu items etc. ------------------- +//----------------------------------------------------------------------------- + +nsMsgSearchValidityTable::nsMsgSearchValidityTable() { + // Set everything to be unavailable and disabled + for (int i = 0; i < nsMsgSearchAttrib::kNumMsgSearchAttributes; i++) + for (int j = 0; j < nsMsgSearchOp::kNumMsgSearchOperators; j++) { + SetAvailable(i, j, false); + SetEnabled(i, j, false); + SetValidButNotShown(i, j, false); + } + m_numAvailAttribs = + 0; // # of attributes marked with at least one available operator + // assume default is Subject, which it is for mail and news search + // it's not for LDAP, so we'll call SetDefaultAttrib() + m_defaultAttrib = nsMsgSearchAttrib::Subject; +} + +NS_IMPL_ISUPPORTS(nsMsgSearchValidityTable, nsIMsgSearchValidityTable) + +nsresult nsMsgSearchValidityTable::GetNumAvailAttribs(int32_t* aResult) { + m_numAvailAttribs = 0; + for (int i = 0; i < nsMsgSearchAttrib::kNumMsgSearchAttributes; i++) + for (int j = 0; j < nsMsgSearchOp::kNumMsgSearchOperators; j++) { + bool available; + GetAvailable(i, j, &available); + if (available) { + m_numAvailAttribs++; + break; + } + } + *aResult = m_numAvailAttribs; + return NS_OK; +} + +nsresult nsMsgSearchValidityTable::GetAvailableAttributes( + nsTArray<nsMsgSearchAttribValue>& aResult) { + aResult.Clear(); + int32_t i, j; + for (i = 0; i < nsMsgSearchAttrib::kNumMsgSearchAttributes; i++) { + for (j = 0; j < nsMsgSearchOp::kNumMsgSearchOperators; j++) { + if (m_table[i][j].bitAvailable) { + aResult.AppendElement(static_cast<nsMsgSearchAttribValue>(i)); + break; + } + } + } + return NS_OK; +} + +nsresult nsMsgSearchValidityTable::GetAvailableOperators( + nsMsgSearchAttribValue aAttribute, nsTArray<nsMsgSearchOpValue>& aResult) { + aResult.Clear(); + + nsMsgSearchAttribValue attr; + if (aAttribute == nsMsgSearchAttrib::Default) + attr = m_defaultAttrib; + else + attr = std::min(aAttribute, + (nsMsgSearchAttribValue)nsMsgSearchAttrib::OtherHeader); + + int32_t i; + for (i = 0; i < nsMsgSearchOp::kNumMsgSearchOperators; i++) { + if (m_table[attr][i].bitAvailable) { + aResult.AppendElement(static_cast<nsMsgSearchOpValue>(i)); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchValidityTable::SetDefaultAttrib(nsMsgSearchAttribValue aAttribute) { + m_defaultAttrib = aAttribute; + return NS_OK; +} + +nsMsgSearchValidityManager::nsMsgSearchValidityManager() {} + +nsMsgSearchValidityManager::~nsMsgSearchValidityManager() { + // tables released by nsCOMPtr +} + +NS_IMPL_ISUPPORTS(nsMsgSearchValidityManager, nsIMsgSearchValidityManager) + +//----------------------------------------------------------------------------- +// Bottleneck accesses to the objects so we can allocate and initialize them +// lazily. This way, there's no heap overhead for the validity tables until the +// user actually searches that scope. +//----------------------------------------------------------------------------- + +NS_IMETHODIMP nsMsgSearchValidityManager::GetTable( + int whichTable, nsIMsgSearchValidityTable** ppOutTable) { + NS_ENSURE_ARG_POINTER(ppOutTable); + + nsresult rv; + *ppOutTable = nullptr; + + nsCOMPtr<nsIPrefBranch> pref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + nsCString customHeaders; + if (NS_SUCCEEDED(rv)) pref->GetCharPref(PREF_CUSTOM_HEADERS, customHeaders); + + switch (whichTable) { + case nsMsgSearchScope::offlineMail: + if (!m_offlineMailTable) rv = InitOfflineMailTable(); + if (m_offlineMailTable) + rv = SetOtherHeadersInTable(m_offlineMailTable, customHeaders.get()); + *ppOutTable = m_offlineMailTable; + break; + case nsMsgSearchScope::offlineMailFilter: + if (!m_offlineMailFilterTable) rv = InitOfflineMailFilterTable(); + if (m_offlineMailFilterTable) + rv = SetOtherHeadersInTable(m_offlineMailFilterTable, + customHeaders.get()); + *ppOutTable = m_offlineMailFilterTable; + break; + case nsMsgSearchScope::onlineMail: + if (!m_onlineMailTable) rv = InitOnlineMailTable(); + if (m_onlineMailTable) + rv = SetOtherHeadersInTable(m_onlineMailTable, customHeaders.get()); + *ppOutTable = m_onlineMailTable; + break; + case nsMsgSearchScope::onlineMailFilter: + if (!m_onlineMailFilterTable) rv = InitOnlineMailFilterTable(); + if (m_onlineMailFilterTable) + rv = SetOtherHeadersInTable(m_onlineMailFilterTable, + customHeaders.get()); + *ppOutTable = m_onlineMailFilterTable; + break; + case nsMsgSearchScope::news: + if (!m_newsTable) rv = InitNewsTable(); + if (m_newsTable) + rv = SetOtherHeadersInTable(m_newsTable, customHeaders.get()); + *ppOutTable = m_newsTable; + break; + case nsMsgSearchScope::newsFilter: + if (!m_newsFilterTable) rv = InitNewsFilterTable(); + if (m_newsFilterTable) + rv = SetOtherHeadersInTable(m_newsFilterTable, customHeaders.get()); + *ppOutTable = m_newsFilterTable; + break; + case nsMsgSearchScope::localNews: + if (!m_localNewsTable) rv = InitLocalNewsTable(); + if (m_localNewsTable) + rv = SetOtherHeadersInTable(m_localNewsTable, customHeaders.get()); + *ppOutTable = m_localNewsTable; + break; + case nsMsgSearchScope::localNewsJunk: + if (!m_localNewsJunkTable) rv = InitLocalNewsJunkTable(); + if (m_localNewsJunkTable) + rv = SetOtherHeadersInTable(m_localNewsJunkTable, customHeaders.get()); + *ppOutTable = m_localNewsJunkTable; + break; + case nsMsgSearchScope::localNewsBody: + if (!m_localNewsBodyTable) rv = InitLocalNewsBodyTable(); + if (m_localNewsBodyTable) + rv = SetOtherHeadersInTable(m_localNewsBodyTable, customHeaders.get()); + *ppOutTable = m_localNewsBodyTable; + break; + case nsMsgSearchScope::localNewsJunkBody: + if (!m_localNewsJunkBodyTable) rv = InitLocalNewsJunkBodyTable(); + if (m_localNewsJunkBodyTable) + rv = SetOtherHeadersInTable(m_localNewsJunkBodyTable, + customHeaders.get()); + *ppOutTable = m_localNewsJunkBodyTable; + break; + + case nsMsgSearchScope::onlineManual: + if (!m_onlineManualFilterTable) rv = InitOnlineManualFilterTable(); + if (m_onlineManualFilterTable) + rv = SetOtherHeadersInTable(m_onlineManualFilterTable, + customHeaders.get()); + *ppOutTable = m_onlineManualFilterTable; + break; + case nsMsgSearchScope::LDAP: + if (!m_ldapTable) rv = InitLdapTable(); + *ppOutTable = m_ldapTable; + break; + case nsMsgSearchScope::LDAPAnd: + if (!m_ldapAndTable) rv = InitLdapAndTable(); + *ppOutTable = m_ldapAndTable; + break; + case nsMsgSearchScope::LocalAB: + if (!m_localABTable) rv = InitLocalABTable(); + *ppOutTable = m_localABTable; + break; + case nsMsgSearchScope::LocalABAnd: + if (!m_localABAndTable) rv = InitLocalABAndTable(); + *ppOutTable = m_localABAndTable; + break; + default: + NS_ASSERTION(false, "invalid table type"); + rv = NS_MSG_ERROR_INVALID_SEARCH_TERM; + } + + NS_IF_ADDREF(*ppOutTable); // Was populated from member variable. + return rv; +} + +// mapping between ordered attribute values, and property strings +// see search-attributes.properties +static struct { + nsMsgSearchAttribValue id; + const char* property; +} nsMsgSearchAttribMap[] = { + {nsMsgSearchAttrib::Subject, "Subject"}, + {nsMsgSearchAttrib::Sender, "From"}, + {nsMsgSearchAttrib::Body, "Body"}, + {nsMsgSearchAttrib::Date, "Date"}, + {nsMsgSearchAttrib::Priority, "Priority"}, + {nsMsgSearchAttrib::MsgStatus, "Status"}, + {nsMsgSearchAttrib::To, "To"}, + {nsMsgSearchAttrib::CC, "Cc"}, + {nsMsgSearchAttrib::ToOrCC, "ToOrCc"}, + {nsMsgSearchAttrib::AgeInDays, "AgeInDays"}, + {nsMsgSearchAttrib::Size, "SizeKB"}, + {nsMsgSearchAttrib::Keywords, "Tags"}, + {nsMsgSearchAttrib::Name, "AnyName"}, + {nsMsgSearchAttrib::DisplayName, "DisplayName"}, + {nsMsgSearchAttrib::Nickname, "Nickname"}, + {nsMsgSearchAttrib::ScreenName, "ScreenName"}, + {nsMsgSearchAttrib::Email, "Email"}, + {nsMsgSearchAttrib::AdditionalEmail, "AdditionalEmail"}, + {nsMsgSearchAttrib::PhoneNumber, "AnyNumber"}, + {nsMsgSearchAttrib::WorkPhone, "WorkPhone"}, + {nsMsgSearchAttrib::HomePhone, "HomePhone"}, + {nsMsgSearchAttrib::Fax, "Fax"}, + {nsMsgSearchAttrib::Pager, "Pager"}, + {nsMsgSearchAttrib::Mobile, "Mobile"}, + {nsMsgSearchAttrib::City, "City"}, + {nsMsgSearchAttrib::Street, "Street"}, + {nsMsgSearchAttrib::Title, "Title"}, + {nsMsgSearchAttrib::Organization, "Organization"}, + {nsMsgSearchAttrib::Department, "Department"}, + {nsMsgSearchAttrib::AllAddresses, "FromToCcOrBcc"}, + {nsMsgSearchAttrib::JunkScoreOrigin, "JunkScoreOrigin"}, + {nsMsgSearchAttrib::JunkPercent, "JunkPercent"}, + {nsMsgSearchAttrib::HasAttachmentStatus, "AttachmentStatus"}, + {nsMsgSearchAttrib::JunkStatus, "JunkStatus"}, + {nsMsgSearchAttrib::OtherHeader, "Customize"}, + // the last id is -1 to denote end of table + {-1, ""}}; + +NS_IMETHODIMP +nsMsgSearchValidityManager::GetAttributeProperty( + nsMsgSearchAttribValue aSearchAttribute, nsAString& aProperty) { + for (int32_t i = 0; nsMsgSearchAttribMap[i].id >= 0; ++i) { + if (nsMsgSearchAttribMap[i].id == aSearchAttribute) { + aProperty.Assign(NS_ConvertUTF8toUTF16(nsMsgSearchAttribMap[i].property)); + return NS_OK; + } + } + return NS_ERROR_FAILURE; +} + +nsresult nsMsgSearchValidityManager::NewTable( + nsIMsgSearchValidityTable** aTable) { + NS_ENSURE_ARG_POINTER(aTable); + NS_ADDREF(*aTable = new nsMsgSearchValidityTable); + return NS_OK; +} + +nsresult nsMsgSearchValidityManager::SetOtherHeadersInTable( + nsIMsgSearchValidityTable* aTable, const char* customHeaders) { + uint32_t customHeadersLength = strlen(customHeaders); + uint32_t numHeaders = 0; + if (customHeadersLength) { + nsAutoCString hdrStr(customHeaders); + hdrStr.StripWhitespace(); // remove whitespace before parsing + char* newStr = hdrStr.BeginWriting(); + char* token = NS_strtok(":", &newStr); + while (token) { + numHeaders++; + token = NS_strtok(":", &newStr); + } + } + + NS_ASSERTION(nsMsgSearchAttrib::OtherHeader + numHeaders < + nsMsgSearchAttrib::kNumMsgSearchAttributes, + "more headers than the table can hold"); + + uint32_t maxHdrs = + std::min(nsMsgSearchAttrib::OtherHeader + numHeaders + 1, + (uint32_t)nsMsgSearchAttrib::kNumMsgSearchAttributes); + for (uint32_t i = nsMsgSearchAttrib::OtherHeader + 1; i < maxHdrs; i++) { + // clang-format off + aTable->SetAvailable(i, nsMsgSearchOp::Contains, 1); // added for arbitrary headers + aTable->SetEnabled (i, nsMsgSearchOp::Contains, 1); + aTable->SetAvailable(i, nsMsgSearchOp::DoesntContain, 1); + aTable->SetEnabled (i, nsMsgSearchOp::DoesntContain, 1); + aTable->SetAvailable(i, nsMsgSearchOp::Is, 1); + aTable->SetEnabled (i, nsMsgSearchOp::Is, 1); + aTable->SetAvailable(i, nsMsgSearchOp::Isnt, 1); + aTable->SetEnabled (i, nsMsgSearchOp::Isnt, 1); + // clang-format on + } + // because custom headers can change; so reset the table for those which are + // no longer used. + for (uint32_t j = maxHdrs; j < nsMsgSearchAttrib::kNumMsgSearchAttributes; + j++) { + for (uint32_t k = 0; k < nsMsgSearchOp::kNumMsgSearchOperators; k++) { + aTable->SetAvailable(j, k, 0); + aTable->SetEnabled(j, k, 0); + } + } + return NS_OK; +} + +nsresult nsMsgSearchValidityManager::EnableDirectoryAttribute( + nsIMsgSearchValidityTable* table, nsMsgSearchAttribValue aSearchAttrib) { + // clang-format off + table->SetAvailable(aSearchAttrib, nsMsgSearchOp::Contains, 1); + table->SetEnabled (aSearchAttrib, nsMsgSearchOp::Contains, 1); + table->SetAvailable(aSearchAttrib, nsMsgSearchOp::DoesntContain, 1); + table->SetEnabled (aSearchAttrib, nsMsgSearchOp::DoesntContain, 1); + table->SetAvailable(aSearchAttrib, nsMsgSearchOp::Is, 1); + table->SetEnabled (aSearchAttrib, nsMsgSearchOp::Is, 1); + table->SetAvailable(aSearchAttrib, nsMsgSearchOp::Isnt, 1); + table->SetEnabled (aSearchAttrib, nsMsgSearchOp::Isnt, 1); + table->SetAvailable(aSearchAttrib, nsMsgSearchOp::BeginsWith, 1); + table->SetEnabled (aSearchAttrib, nsMsgSearchOp::BeginsWith, 1); + table->SetAvailable(aSearchAttrib, nsMsgSearchOp::EndsWith, 1); + table->SetEnabled (aSearchAttrib, nsMsgSearchOp::EndsWith, 1); + table->SetAvailable(aSearchAttrib, nsMsgSearchOp::SoundsLike, 1); + table->SetEnabled (aSearchAttrib, nsMsgSearchOp::SoundsLike, 1); + // clang-format on + return NS_OK; +} + +nsresult nsMsgSearchValidityManager::InitLdapTable() { + NS_ASSERTION(!m_ldapTable, "don't call this twice!"); + + nsresult rv = NewTable(getter_AddRefs(m_ldapTable)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = SetUpABTable(m_ldapTable, true); + NS_ENSURE_SUCCESS(rv, rv); + return rv; +} + +nsresult nsMsgSearchValidityManager::InitLdapAndTable() { + NS_ASSERTION(!m_ldapAndTable, "don't call this twice!"); + + nsresult rv = NewTable(getter_AddRefs(m_ldapAndTable)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = SetUpABTable(m_ldapAndTable, false); + NS_ENSURE_SUCCESS(rv, rv); + return rv; +} + +nsresult nsMsgSearchValidityManager::InitLocalABTable() { + NS_ASSERTION(!m_localABTable, "don't call this twice!"); + + nsresult rv = NewTable(getter_AddRefs(m_localABTable)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = SetUpABTable(m_localABTable, true); + NS_ENSURE_SUCCESS(rv, rv); + return rv; +} + +nsresult nsMsgSearchValidityManager::InitLocalABAndTable() { + NS_ASSERTION(!m_localABAndTable, "don't call this twice!"); + + nsresult rv = NewTable(getter_AddRefs(m_localABAndTable)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = SetUpABTable(m_localABAndTable, false); + NS_ENSURE_SUCCESS(rv, rv); + return rv; +} + +nsresult nsMsgSearchValidityManager::SetUpABTable( + nsIMsgSearchValidityTable* aTable, bool isOrTable) { + nsresult rv = aTable->SetDefaultAttrib( + isOrTable ? nsMsgSearchAttrib::Name : nsMsgSearchAttrib::DisplayName); + NS_ENSURE_SUCCESS(rv, rv); + + if (isOrTable) { + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Name); + NS_ENSURE_SUCCESS(rv, rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::PhoneNumber); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::DisplayName); + NS_ENSURE_SUCCESS(rv, rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Email); + NS_ENSURE_SUCCESS(rv, rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::AdditionalEmail); + NS_ENSURE_SUCCESS(rv, rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::ScreenName); + NS_ENSURE_SUCCESS(rv, rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Street); + NS_ENSURE_SUCCESS(rv, rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::City); + NS_ENSURE_SUCCESS(rv, rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Title); + NS_ENSURE_SUCCESS(rv, rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Organization); + NS_ENSURE_SUCCESS(rv, rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Department); + NS_ENSURE_SUCCESS(rv, rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Nickname); + NS_ENSURE_SUCCESS(rv, rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::WorkPhone); + NS_ENSURE_SUCCESS(rv, rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::HomePhone); + NS_ENSURE_SUCCESS(rv, rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Fax); + NS_ENSURE_SUCCESS(rv, rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Pager); + NS_ENSURE_SUCCESS(rv, rv); + + rv = EnableDirectoryAttribute(aTable, nsMsgSearchAttrib::Mobile); + NS_ENSURE_SUCCESS(rv, rv); + + return rv; +} |