diff options
Diffstat (limited to 'comm/mailnews/search/src')
30 files changed, 10927 insertions, 0 deletions
diff --git a/comm/mailnews/search/src/Bogofilter.sfd b/comm/mailnews/search/src/Bogofilter.sfd new file mode 100644 index 0000000000..a29a818e69 --- /dev/null +++ b/comm/mailnews/search/src/Bogofilter.sfd @@ -0,0 +1,14 @@ +version="9" +logging="yes" +name="BogofilterYes" +enabled="yes" +type="17" +action="JunkScore" +actionValue="100" +condition="OR (\"X-Bogosity\",begins with,Spam) OR (\"X-Bogosity\",begins with,Y)" +name="BogofilterNo" +enabled="yes" +type="17" +action="JunkScore" +actionValue="0" +condition="OR (\"X-Bogosity\",begins with,Ham) OR (\"X-Bogosity\",begins with,N)" diff --git a/comm/mailnews/search/src/DSPAM.sfd b/comm/mailnews/search/src/DSPAM.sfd new file mode 100644 index 0000000000..40bc00df78 --- /dev/null +++ b/comm/mailnews/search/src/DSPAM.sfd @@ -0,0 +1,14 @@ +version="9" +logging="yes" +name="DSPAMYes" +enabled="yes" +type="17" +action="JunkScore" +actionValue="100" +condition="OR (\"X-DSPAM-Result\",begins with,Spam)" +name="DSPAMNo" +enabled="yes" +type="17" +action="JunkScore" +actionValue="0" +condition="OR (\"X-DSPAM-Result\",begins with,Innocent)" diff --git a/comm/mailnews/search/src/Habeas.sfd b/comm/mailnews/search/src/Habeas.sfd new file mode 100644 index 0000000000..ceffff16e5 --- /dev/null +++ b/comm/mailnews/search/src/Habeas.sfd @@ -0,0 +1,8 @@ +version="8" +logging="yes" +name="HabeasNo" +enabled="yes" +type="1" +action="JunkScore" +actionValue="0" +condition="OR (\"X-Habeas-SWE-3\",is,\"like Habeas SWE (tm)\")" diff --git a/comm/mailnews/search/src/MsgTraitService.jsm b/comm/mailnews/search/src/MsgTraitService.jsm new file mode 100644 index 0000000000..c44b015e38 --- /dev/null +++ b/comm/mailnews/search/src/MsgTraitService.jsm @@ -0,0 +1,199 @@ +/* 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/. */ + +var EXPORTED_SYMBOLS = ["MsgTraitService"]; + +// local static variables + +var _lastIndex = 0; // the first index will be one +var _traits = {}; + +var traitsBranch = Services.prefs.getBranch("mailnews.traits."); + +function _registerTrait(aId, aIndex) { + var trait = {}; + trait.enabled = false; + trait.name = ""; + trait.antiId = ""; + trait.index = aIndex; + _traits[aId] = trait; +} + +function MsgTraitService() {} + +MsgTraitService.prototype = { + // Component setup + QueryInterface: ChromeUtils.generateQI(["nsIMsgTraitService"]), + + // nsIMsgTraitService implementation + + get lastIndex() { + return _lastIndex; + }, + + registerTrait(aId) { + if (_traits[aId]) { + // Meaning already registered. + return 0; + } + _registerTrait(aId, ++_lastIndex); + traitsBranch.setBoolPref("enabled." + _lastIndex, false); + traitsBranch.setCharPref("id." + _lastIndex, aId); + return _lastIndex; + }, + + unRegisterTrait(aId) { + if (_traits[aId]) { + var index = _traits[aId].index; + _traits[aId] = null; + traitsBranch.clearUserPref("id." + index); + traitsBranch.clearUserPref("enabled." + index); + traitsBranch.clearUserPref("antiId." + index); + traitsBranch.clearUserPref("name." + index); + } + }, + + isRegistered(aId) { + return !!_traits[aId]; + }, + + setName(aId, aName) { + traitsBranch.setCharPref("name." + _traits[aId].index, aName); + _traits[aId].name = aName; + }, + + getName(aId) { + return _traits[aId].name; + }, + + getIndex(aId) { + return _traits[aId].index; + }, + + getId(aIndex) { + for (let id in _traits) { + if (_traits[id].index == aIndex) { + return id; + } + } + return null; + }, + + setEnabled(aId, aEnabled) { + traitsBranch.setBoolPref("enabled." + _traits[aId].index, aEnabled); + _traits[aId].enabled = aEnabled; + }, + + getEnabled(aId) { + return _traits[aId].enabled; + }, + + setAntiId(aId, aAntiId) { + traitsBranch.setCharPref("antiId." + _traits[aId].index, aAntiId); + _traits[aId].antiId = aAntiId; + }, + + getAntiId(aId) { + return _traits[aId].antiId; + }, + + getEnabledProIndices() { + let proIndices = []; + for (let id in _traits) { + if (_traits[id].enabled) { + proIndices.push(_traits[id].index); + } + } + return proIndices; + }, + + getEnabledAntiIndices() { + let antiIndices = []; + for (let id in _traits) { + if (_traits[id].enabled) { + antiIndices.push(_traits[_traits[id].antiId].index); + } + } + return antiIndices; + }, + + addAlias(aTraitIndex, aTraitAliasIndex) { + let aliasesString = traitsBranch.getCharPref("aliases." + aTraitIndex, ""); + let aliases; + if (aliasesString.length) { + aliases = aliasesString.split(","); + } else { + aliases = []; + } + if (!aliases.includes(aTraitAliasIndex.toString())) { + aliases.push(aTraitAliasIndex); + traitsBranch.setCharPref("aliases." + aTraitIndex, aliases.join()); + } + }, + + removeAlias(aTraitIndex, aTraitAliasIndex) { + let aliasesString = traitsBranch.getCharPref("aliases." + aTraitIndex, ""); + let aliases; + if (aliasesString.length) { + aliases = aliasesString.split(","); + } else { + aliases = []; + } + let location = aliases.indexOf(aTraitAliasIndex.toString()); + if (location != -1) { + aliases.splice(location, 1); + traitsBranch.setCharPref("aliases." + aTraitIndex, aliases.join()); + } + }, + + getAliases(aTraitIndex) { + let aliasesString = traitsBranch.getCharPref("aliases." + aTraitIndex, ""); + let aliases; + if (aliasesString.length) { + aliases = aliasesString.split(","); + } else { + aliases = []; + } + return aliases; + }, +}; + +// initialization + +_init(); + +function _init() { + // get existing traits + var idBranch = Services.prefs.getBranch("mailnews.traits.id."); + var nameBranch = Services.prefs.getBranch("mailnews.traits.name."); + var enabledBranch = Services.prefs.getBranch("mailnews.traits.enabled."); + var antiIdBranch = Services.prefs.getBranch("mailnews.traits.antiId."); + _lastIndex = Services.prefs + .getBranch("mailnews.traits.") + .getIntPref("lastIndex"); + var ids = idBranch.getChildList(""); + for (let i = 0; i < ids.length; i++) { + var id = idBranch.getCharPref(ids[i]); + var index = parseInt(ids[i]); + _registerTrait(id, index, false); + + if (nameBranch.getPrefType(ids[i]) == Services.prefs.PREF_STRING) { + _traits[id].name = nameBranch.getCharPref(ids[i]); + } + if (enabledBranch.getPrefType(ids[i]) == Services.prefs.PREF_BOOL) { + _traits[id].enabled = enabledBranch.getBoolPref(ids[i]); + } + if (antiIdBranch.getPrefType(ids[i]) == Services.prefs.PREF_STRING) { + _traits[id].antiId = antiIdBranch.getCharPref(ids[i]); + } + + if (_lastIndex < index) { + _lastIndex = index; + } + } + + // for (traitId in _traits) + // dump("\nindex of " + traitId + " is " + _traits[traitId].index); + // dump("\n"); +} diff --git a/comm/mailnews/search/src/POPFile.sfd b/comm/mailnews/search/src/POPFile.sfd new file mode 100644 index 0000000000..a791705aa4 --- /dev/null +++ b/comm/mailnews/search/src/POPFile.sfd @@ -0,0 +1,14 @@ +version="9" +logging="yes" +name="POPFileYes" +enabled="yes" +type="17" +action="JunkScore" +actionValue="100" +condition="OR (\"X-Text-Classification\",begins with,spam)" +name="POPFileNo" +enabled="yes" +type="17" +action="JunkScore" +actionValue="0" +condition="OR (\"X-Text-Classification\",begins with,inbox) OR (\"X-Text-Classification\",begins with,allowed)" diff --git a/comm/mailnews/search/src/PeriodicFilterManager.jsm b/comm/mailnews/search/src/PeriodicFilterManager.jsm new file mode 100644 index 0000000000..3d6f52c504 --- /dev/null +++ b/comm/mailnews/search/src/PeriodicFilterManager.jsm @@ -0,0 +1,202 @@ +/* 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/. */ + +/* + * Execute periodic filters at the correct rate. + * + * The only external call required for this is setupFiltering(). This should be + * called before the mail-startup-done notification. + */ + +const EXPORTED_SYMBOLS = ["PeriodicFilterManager"]; + +const { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +const log = console.createInstance({ + prefix: "mail.periodicfilters", + maxLogLevel: "Warn", + maxLogLevelPref: "mail.periodicfilters.loglevel", +}); + +var PeriodicFilterManager = { + _timer: null, + _checkRateMilliseconds: 60000, // How often do we check if servers are ready to run? + _defaultFilterRateMinutes: Services.prefs + .getDefaultBranch("") + .getIntPref("mail.server.default.periodicFilterRateMinutes"), + _initialized: false, // Has this been initialized? + _running: false, // Are we executing filters already? + + // Initial call to begin startup. + setupFiltering() { + if (this._initialized) { + return; + } + + this._initialized = true; + Services.obs.addObserver(this, "mail-startup-done"); + }, + + // Main call to start the periodic filter process + init() { + log.info("PeriodicFilterManager init()"); + // set the next filter time + for (let server of MailServices.accounts.allServers) { + let nowTime = parseInt(Date.now() / 60000); + // Make sure that the last filter time of all servers was in the past. + let lastFilterTime = server.getIntValue("lastFilterTime"); + // Schedule next filter run. + let nextFilterTime = + lastFilterTime < nowTime + ? lastFilterTime + this.getServerPeriod(server) + : nowTime; + server.setIntValue("nextFilterTime", nextFilterTime); + } + + // kickoff the timer to run periodic filters + this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + this._timer.initWithCallback( + this, + this._checkRateMilliseconds, + Ci.nsITimer.TYPE_REPEATING_SLACK + ); + Services.obs.addObserver(this, "quit-application-granted"); + }, + + /** + * Periodic callback to check if any periodic filters need to be run. + * + * The periodic filter manager does not guarantee that filters will be run + * precisely at the specified interval. + * The server may be busy (e.g. downloading messages) or another filter run + * is still ongoing, in which cases running periodic filter of any server + * may be postponed. + */ + notify(timer) { + log.debug("PeriodicFilterManager timer callback"); + if (this._running) { + log.debug("PeriodicFilterManager Previous filter run still executing"); + return; + } + this._running = true; + let nowTime = parseInt(Date.now() / 60000); + for (let server of MailServices.accounts.allServers) { + if (!server.canHaveFilters) { + continue; + } + if (server.getIntValue("nextFilterTime") > nowTime) { + continue; + } + if (server.serverBusy) { + continue; + } + + // Schedule next time this account's filters should be run. + server.setIntValue( + "nextFilterTime", + nowTime + this.getServerPeriod(server) + ); + server.setIntValue("lastFilterTime", nowTime); + + // Build a temporary list of periodic filters. + // XXX TODO: make applyFiltersToFolders() take a filterType instead (bug 1551043). + let curFilterList = server.getFilterList(null); + let tempFilterList = MailServices.filters.getTempFilterList( + server.rootFolder + ); + let numFilters = curFilterList.filterCount; + tempFilterList.loggingEnabled = curFilterList.loggingEnabled; + tempFilterList.logStream = curFilterList.logStream; + let newFilterIndex = 0; + for (let i = 0; i < numFilters; i++) { + let curFilter = curFilterList.getFilterAt(i); + // Only add enabled, UI visible filters that are of the Periodic type. + if ( + curFilter.enabled && + !curFilter.temporary && + curFilter.filterType & Ci.nsMsgFilterType.Periodic + ) { + tempFilterList.insertFilterAt(newFilterIndex, curFilter); + newFilterIndex++; + } + } + if (newFilterIndex == 0) { + continue; + } + let foldersToFilter = server.rootFolder.getFoldersWithFlags( + Ci.nsMsgFolderFlags.Inbox + ); + if (foldersToFilter.length == 0) { + continue; + } + + log.debug( + "PeriodicFilterManager apply periodic filters to server " + + server.prettyName + ); + MailServices.filters.applyFiltersToFolders( + tempFilterList, + foldersToFilter, + null + ); + } + this._running = false; + }, + + /** + * Gets the periodic filter interval for the given server. + * If the server's interval is not sane, clean it up. + * + * @param {nsIMsgIncomingServer} server - The server to return interval for. + */ + getServerPeriod(server) { + const minimumPeriodMinutes = 1; + let serverRateMinutes = server.getIntValue("periodicFilterRateMinutes"); + // Check if period is too short. + if (serverRateMinutes < minimumPeriodMinutes) { + // If the server.default pref is too low, clear that one first. + if ( + Services.prefs.getIntPref( + "mail.server.default.periodicFilterRateMinutes" + ) == serverRateMinutes + ) { + Services.prefs.clearUserPref( + "mail.server.default.periodicFilterRateMinutes" + ); + } + // If the server still has its own specific value and it is still too low, sanitize it. + if ( + server.getIntValue("periodicFilterRateMinutes") < minimumPeriodMinutes + ) { + server.setIntValue( + "periodicFilterRateMinutes", + this._defaultFilterRateMinutes + ); + } + + return this._defaultFilterRateMinutes; + } + + return serverRateMinutes; + }, + + observe(subject, topic, data) { + Services.obs.removeObserver(this, topic); + if (topic == "mail-startup-done") { + this.init(); + } else if (topic == "quit-application-granted") { + this.shutdown(); + } + }, + + shutdown() { + log.info("PeriodicFilterManager shutdown"); + if (this._timer) { + this._timer.cancel(); + this._timer = null; + } + }, +}; diff --git a/comm/mailnews/search/src/SpamAssassin.sfd b/comm/mailnews/search/src/SpamAssassin.sfd new file mode 100644 index 0000000000..d8d0ecdb10 --- /dev/null +++ b/comm/mailnews/search/src/SpamAssassin.sfd @@ -0,0 +1,14 @@ +version="9" +logging="yes" +name="SpamAssassinYes" +enabled="yes" +type="17" +action="JunkScore" +actionValue="100" +condition="OR (\"X-Spam-Status\",begins with,Yes) OR (\"X-Spam-Flag\",begins with,YES) OR (subject,begins with,***SPAM***)" +name="SpamAssassinNo" +enabled="yes" +type="17" +action="JunkScore" +actionValue="0" +condition="OR (\"X-Spam-Status\",begins with,No)" diff --git a/comm/mailnews/search/src/SpamCatcher.sfd b/comm/mailnews/search/src/SpamCatcher.sfd new file mode 100644 index 0000000000..d16cd80a28 --- /dev/null +++ b/comm/mailnews/search/src/SpamCatcher.sfd @@ -0,0 +1,14 @@ +version="9" +logging="yes" +name="SpamCatcherNo" +enabled="yes" +type="17" +action="JunkScore" +actionValue="0" +condition="OR (\"x-SpamCatcher\",begins with,No)" +name="SpamCatcherYes" +enabled="yes" +type="17" +action="JunkScore" +actionValue="100" +condition="OR (\"x-SpamCatcher\",begins with,Yes)" diff --git a/comm/mailnews/search/src/SpamPal.sfd b/comm/mailnews/search/src/SpamPal.sfd new file mode 100644 index 0000000000..830b1937b5 --- /dev/null +++ b/comm/mailnews/search/src/SpamPal.sfd @@ -0,0 +1,14 @@ +version="9" +logging="yes" +name="SpamPalNo" +enabled="yes" +type="17" +action="JunkScore" +actionValue="0" +condition="OR (\"X-SpamPal\",begins with,PASS)" +name="SpamPalYes" +enabled="yes" +type="17" +action="JunkScore" +actionValue="100" +condition="OR (\"X-SpamPal\",begins with,SPAM)" diff --git a/comm/mailnews/search/src/components.conf b/comm/mailnews/search/src/components.conf new file mode 100644 index 0000000000..943b3fb542 --- /dev/null +++ b/comm/mailnews/search/src/components.conf @@ -0,0 +1,40 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +Classes = [ + { + "cid": "{a2e95f4f-da72-4a41-9493-661ad353c00a}", + "contract_ids": ["@mozilla.org/msg-trait-service;1"], + "jsm": "resource:///modules/MsgTraitService.jsm", + "constructor": "MsgTraitService", + }, + { + "cid": "{5cbb0700-04bc-11d3-a50a-0060b0fc04b7}", + "contract_ids": ["@mozilla.org/messenger/services/filters;1"], + "type": "nsMsgFilterService", + "headers": ["/comm/mailnews/search/src/nsMsgFilterService.h"], + "name": "Filter", + "interfaces": ["nsIMsgFilterService"], + }, + { + "cid": "{e9a7cd70-0303-11d3-a50a-0060b0fc04b7}", + "contract_ids": ["@mozilla.org/messenger/searchSession;1"], + "type": "nsMsgSearchSession", + "headers": ["/comm/mailnews/search/src/nsMsgSearchSession.h"], + }, + { + "cid": "{e1da397d-fdc5-4b23-a6fe-d46a034d80b3}", + "contract_ids": ["@mozilla.org/messenger/searchTerm;1"], + "type": "nsMsgSearchTerm", + "headers": ["/comm/mailnews/search/public/nsMsgSearchTerm.h"], + }, + { + "cid": "{1510faee-ad1a-4194-8039-33de32d5a882}", + "contract_ids": ["@mozilla.org/mail/search/validityManager;1"], + "type": "nsMsgSearchValidityManager", + "headers": ["/comm/mailnews/search/public/nsMsgSearchAdapter.h"], + }, +] diff --git a/comm/mailnews/search/src/moz.build b/comm/mailnews/search/src/moz.build new file mode 100644 index 0000000000..b8f879aa15 --- /dev/null +++ b/comm/mailnews/search/src/moz.build @@ -0,0 +1,37 @@ +# vim: set filetype=python: +# 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/. + +SOURCES += [ + "nsMsgBodyHandler.cpp", + "nsMsgFilter.cpp", + "nsMsgFilterList.cpp", + "nsMsgFilterService.cpp", + "nsMsgImapSearch.cpp", + "nsMsgLocalSearch.cpp", + "nsMsgSearchAdapter.cpp", + "nsMsgSearchNews.cpp", + "nsMsgSearchSession.cpp", + "nsMsgSearchTerm.cpp", + "nsMsgSearchValue.cpp", +] + +EXTRA_JS_MODULES += [ + "MsgTraitService.jsm", + "PeriodicFilterManager.jsm", +] + +XPCOM_MANIFESTS += [ + "components.conf", +] + +FINAL_LIBRARY = "mail" + +FINAL_TARGET_FILES.isp += [ + "Bogofilter.sfd", + "DSPAM.sfd", + "POPFile.sfd", + "SpamAssassin.sfd", + "SpamPal.sfd", +] diff --git a/comm/mailnews/search/src/nsMsgBodyHandler.cpp b/comm/mailnews/search/src/nsMsgBodyHandler.cpp new file mode 100644 index 0000000000..5b77750f63 --- /dev/null +++ b/comm/mailnews/search/src/nsMsgBodyHandler.cpp @@ -0,0 +1,464 @@ +/* -*- 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 "nsMsgSearchCore.h" +#include "nsMsgUtils.h" +#include "nsMsgBodyHandler.h" +#include "nsMsgSearchTerm.h" +#include "nsIMsgHdr.h" +#include "nsMsgMessageFlags.h" +#include "nsISeekableStream.h" +#include "nsIInputStream.h" +#include "nsIFile.h" +#include "plbase64.h" +#include "prmem.h" +#include "nsMimeTypes.h" + +nsMsgBodyHandler::nsMsgBodyHandler(nsIMsgSearchScopeTerm* scope, + uint32_t numLines, nsIMsgDBHdr* msg, + nsIMsgDatabase* db) { + m_scope = scope; + m_numLocalLines = numLines; + uint32_t flags; + m_lineCountInBodyLines = NS_SUCCEEDED(msg->GetFlags(&flags)) + ? !(flags & nsMsgMessageFlags::Offline) + : true; + // account for added x-mozilla-status lines, and envelope line. + if (!m_lineCountInBodyLines) m_numLocalLines += 3; + m_msgHdr = msg; + m_db = db; + + // the following are variables used when the body handler is handling stuff + // from filters....through this constructor, that is not the case so we set + // them to NULL. + m_headers = nullptr; + m_headersSize = 0; + m_Filtering = false; // make sure we set this before we call initialize... + + Initialize(); // common initialization stuff + OpenLocalFolder(); +} + +nsMsgBodyHandler::nsMsgBodyHandler(nsIMsgSearchScopeTerm* scope, + uint32_t numLines, nsIMsgDBHdr* msg, + nsIMsgDatabase* db, const char* headers, + uint32_t headersSize, bool Filtering) { + m_scope = scope; + m_numLocalLines = numLines; + uint32_t flags; + m_lineCountInBodyLines = NS_SUCCEEDED(msg->GetFlags(&flags)) + ? !(flags & nsMsgMessageFlags::Offline) + : true; + // account for added x-mozilla-status lines, and envelope line. + if (!m_lineCountInBodyLines) m_numLocalLines += 3; + m_msgHdr = msg; + m_db = db; + m_headers = nullptr; + m_headersSize = 0; + m_Filtering = Filtering; + + Initialize(); + + if (m_Filtering) { + m_headers = headers; + m_headersSize = headersSize; + } else { + OpenLocalFolder(); + } +} + +void nsMsgBodyHandler::Initialize() +// common initialization code regardless of what body type we are handling... +{ + // Default transformations for local message search and MAPI access + m_stripHeaders = true; + m_partIsHtml = false; + m_base64part = false; + m_partIsQP = false; + m_isMultipart = false; + m_partIsText = true; // Default is text/plain, maybe proven otherwise later. + m_pastMsgHeaders = false; + m_pastPartHeaders = false; + m_inMessageAttachment = false; + m_headerBytesRead = 0; +} + +nsMsgBodyHandler::~nsMsgBodyHandler() {} + +int32_t nsMsgBodyHandler::GetNextLine(nsCString& buf, nsCString& charset) { + int32_t length = -1; // length of incoming line or -1 eof + int32_t outLength = -1; // length of outgoing line or -1 eof + bool eatThisLine = true; + nsAutoCString nextLine; + + while (eatThisLine) { + // first, handle the filtering case...this is easy.... + if (m_Filtering) { + length = GetNextFilterLine(nextLine); + } else { + // 3 cases: Offline IMAP, POP, or we are dealing with a news message.... + // Offline cases should be same as local mail cases, since we're going + // to store offline messages in berkeley format folders. + if (m_db) { + length = GetNextLocalLine(nextLine); // (2) POP + } + } + + if (length < 0) break; // eof in + + outLength = ApplyTransformations(nextLine, length, eatThisLine, buf); + } + + if (outLength < 0) return -1; // eof out + + // For non-multipart messages, the entire message minus headers is encoded + // ApplyTransformations can only decode a part + if (!m_isMultipart && m_base64part) { + Base64Decode(buf); + m_base64part = false; + // And reapply our transformations... + outLength = ApplyTransformations(buf, buf.Length(), eatThisLine, buf); + } + + // Process aggregated HTML. + if (!m_isMultipart && m_partIsHtml) { + StripHtml(buf); + outLength = buf.Length(); + } + + charset = m_partCharset; + return outLength; +} + +void nsMsgBodyHandler::OpenLocalFolder() { + nsCOMPtr<nsIInputStream> inputStream; + nsresult rv = m_scope->GetInputStream(m_msgHdr, getter_AddRefs(inputStream)); + // Warn and return if GetInputStream fails + NS_ENSURE_SUCCESS_VOID(rv); + m_fileLineStream = do_QueryInterface(inputStream); +} + +int32_t nsMsgBodyHandler::GetNextFilterLine(nsCString& buf) { + // m_nextHdr always points to the next header in the list....the list is NULL + // terminated... + uint32_t numBytesCopied = 0; + if (m_headersSize > 0) { + // #mscott. Ugly hack! filter headers list have CRs & LFs inside the NULL + // delimited list of header strings. It is possible to have: To NULL CR LF + // From. We want to skip over these CR/LFs if they start at the beginning of + // what we think is another header. + + while (m_headersSize > 0 && (m_headers[0] == '\r' || m_headers[0] == '\n' || + m_headers[0] == ' ' || m_headers[0] == '\0')) { + m_headers++; // skip over these chars... + m_headersSize--; + } + + if (m_headersSize > 0) { + numBytesCopied = strlen(m_headers) + 1; + buf.Assign(m_headers); + m_headers += numBytesCopied; + // be careful...m_headersSize is unsigned. Don't let it go negative or we + // overflow to 2^32....*yikes* + if (m_headersSize < numBytesCopied) + m_headersSize = 0; + else + m_headersSize -= numBytesCopied; // update # bytes we have read from + // the headers list + + return (int32_t)numBytesCopied; + } + } else if (m_headersSize == 0) { + buf.Truncate(); + } + return -1; +} + +// return -1 if no more local lines, length of next line otherwise. + +int32_t nsMsgBodyHandler::GetNextLocalLine(nsCString& buf) +// returns number of bytes copied +{ + if (m_numLocalLines) { + // I the line count is in body lines, only decrement once we have + // processed all the headers. Otherwise the line is not in body + // lines and we want to decrement for every line. + if (m_pastMsgHeaders || !m_lineCountInBodyLines) m_numLocalLines--; + // do we need to check the return value here? + if (m_fileLineStream) { + bool more = false; + nsresult rv = m_fileLineStream->ReadLine(buf, &more); + if (NS_SUCCEEDED(rv)) return buf.Length(); + } + } + + return -1; +} + +/** + * This method applies a sequence of transformations to the line. + * + * It applies the following sequences in order + * * Removes headers if the searcher doesn't want them + * (sets m_past*Headers) + * * Determines the current MIME type. + * (via SniffPossibleMIMEHeader) + * * Strips any HTML if the searcher doesn't want it + * * Strips non-text parts + * * Decodes any base64 part + * (resetting part variables: m_base64part, m_pastPartHeaders, m_partIsHtml, + * m_partIsText) + * + * @param line (in) the current line + * @param length (in) the length of said line + * @param eatThisLine (out) whether or not to ignore this line + * @param buf (inout) if m_base64part, the current part as needed for + * decoding; else, it is treated as an out param (a + * redundant version of line). + * @return the length of the line after applying transformations + */ +int32_t nsMsgBodyHandler::ApplyTransformations(const nsCString& line, + int32_t length, + bool& eatThisLine, + nsCString& buf) { + eatThisLine = false; + + if (!m_pastPartHeaders) // line is a line from the part headers + { + if (m_stripHeaders) eatThisLine = true; + + // We have already grabbed all worthwhile information from the headers, + // so there is no need to keep track of the current lines + buf.Assign(line); + + SniffPossibleMIMEHeader(buf); + + if (buf.IsEmpty() || buf.First() == '\r' || buf.First() == '\n') { + if (!m_inMessageAttachment) { + m_pastPartHeaders = true; + } else { + // We're in a message attachment and have just read past the + // part header for the attached message. We now need to read + // the message headers and any part headers. + // We can now forget about the special handling of attached messages. + m_inMessageAttachment = false; + } + } + + // We set m_pastMsgHeaders to 'true' only once. + if (m_pastPartHeaders) m_pastMsgHeaders = true; + + return length; + } + + // Check to see if this is one of our boundary strings. + bool matchedBoundary = false; + if (m_isMultipart && m_boundaries.Length() > 0) { + for (int32_t i = (int32_t)m_boundaries.Length() - 1; i >= 0; i--) { + if (StringBeginsWith(line, m_boundaries[i])) { + matchedBoundary = true; + // If we matched a boundary, we won't need the nested/later ones any + // more. + m_boundaries.SetLength(i + 1); + break; + } + } + } + if (matchedBoundary) { + if (m_base64part && m_partIsText) { + Base64Decode(buf); + // Work on the parsed string + if (!buf.Length()) { + NS_WARNING("Trying to transform an empty buffer"); + eatThisLine = true; + } else { + // It is wrong to call ApplyTransformations() here since this will + // lead to the buffer being doubled-up at |buf.Append(line);| + // below. ApplyTransformations(buf, buf.Length(), eatThisLine, buf); + // Avoid spurious failures + eatThisLine = false; + } + } else if (!m_partIsHtml) { + buf.Truncate(); + eatThisLine = true; // We have no content... + } + + if (m_partIsHtml) { + StripHtml(buf); + } + + // Reset all assumed headers + m_base64part = false; + // Get ready to sniff new part headers, but do not reset m_pastMsgHeaders + // since it will screw the body line count. + m_pastPartHeaders = false; + m_partIsHtml = false; + // If we ever see a multipart message, each part needs to set + // 'm_partIsText', so no more defaulting to 'true' when the part is done. + m_partIsText = false; + + // Note: we cannot reset 'm_partIsQP' yet since we still need it to process + // the last buffer returned here. Parsing the next part will set a new + // value. + return buf.Length(); + } + + if (!m_partIsText) { + // Ignore non-text parts + buf.Truncate(); + eatThisLine = true; + return 0; + } + + // Accumulate base64 parts and HTML parts for later decoding or tag stripping. + if (m_base64part || m_partIsHtml) { + if (m_partIsHtml && !m_base64part) { + size_t bufLength = buf.Length(); + if (!m_partIsQP || bufLength == 0 || !StringEndsWith(buf, "="_ns)) { + // Replace newline in HTML with a space. + buf.Append(' '); + } else { + // Strip the soft line break. + buf.SetLength(bufLength - 1); + } + } + buf.Append(line); + eatThisLine = true; + return buf.Length(); + } + + buf.Assign(line); + return buf.Length(); +} + +void nsMsgBodyHandler::StripHtml(nsCString& pBufInOut) { + char* pBuf = (char*)PR_Malloc(pBufInOut.Length() + 1); + if (pBuf) { + char* pWalk = pBuf; + + char* pWalkInOut = (char*)pBufInOut.get(); + bool inTag = false; + while (*pWalkInOut) // throw away everything inside < > + { + if (!inTag) { + if (*pWalkInOut == '<') + inTag = true; + else + *pWalk++ = *pWalkInOut; + } else { + if (*pWalkInOut == '>') inTag = false; + } + pWalkInOut++; + } + *pWalk = 0; // null terminator + + pBufInOut.Adopt(pBuf); + } +} + +/** + * Determines the MIME type, if present, from the current line. + * + * m_partIsHtml, m_isMultipart, m_partIsText, m_base64part, and boundary are + * all set by this method at various points in time. + * + * @param line (in) a header line that may contain a MIME header + */ +void nsMsgBodyHandler::SniffPossibleMIMEHeader(const nsCString& line) { + // Some parts of MIME are case-sensitive and other parts are case-insensitive; + // specifically, the headers are all case-insensitive and the values we care + // about are also case-insensitive, with the sole exception of the boundary + // string, so we can't just take the input line and make it lower case. + nsCString lowerCaseLine(line); + ToLowerCase(lowerCaseLine); + + if (StringBeginsWith(lowerCaseLine, "content-transfer-encoding:"_ns)) + m_partIsQP = lowerCaseLine.Find("quoted-printable") != kNotFound; + + if (StringBeginsWith(lowerCaseLine, "content-type:"_ns)) { + if (lowerCaseLine.LowerCaseFindASCII("text/html") != kNotFound) { + m_partIsText = true; + m_partIsHtml = true; + } else if (lowerCaseLine.Find("multipart/") != kNotFound) { + if (m_isMultipart) { + // Nested multipart, get ready for new headers. + m_base64part = false; + m_partIsQP = false; + m_pastPartHeaders = false; + m_partIsHtml = false; + m_partIsText = false; + } + m_isMultipart = true; + m_partCharset.Truncate(); + } else if (lowerCaseLine.Find("message/") != kNotFound) { + // Initialise again. + m_base64part = false; + m_partIsQP = false; + m_pastPartHeaders = false; + m_partIsHtml = false; + m_partIsText = + true; // Default is text/plain, maybe proven otherwise later. + m_inMessageAttachment = true; + } else if (lowerCaseLine.Find("text/") != kNotFound) + m_partIsText = true; + else if (lowerCaseLine.Find("text/") == kNotFound) + m_partIsText = false; // We have disproven our assumption. + } + + int32_t start; + if (m_isMultipart && (start = lowerCaseLine.Find("boundary=")) != kNotFound) { + start += 9; // strlen("boundary=") + if (line[start] == '\"') start++; + int32_t end = line.RFindChar('\"'); + if (end == -1) end = line.Length(); + + // Collect all boundaries. Since we only react to crossing a boundary, + // we can simply collect the boundaries instead of forming a tree + // structure from the message. Keep it simple ;-) + nsCString boundary; + boundary.AssignLiteral("--"); + boundary.Append(Substring(line, start, end - start)); + if (!m_boundaries.Contains(boundary)) m_boundaries.AppendElement(boundary); + } + + if (m_isMultipart && (start = lowerCaseLine.Find("charset=")) != kNotFound) { + start += 8; // strlen("charset=") + bool foundQuote = false; + if (line[start] == '\"') { + start++; + foundQuote = true; + } + int32_t end = line.FindChar(foundQuote ? '\"' : ';', start); + if (end == -1) end = line.Length(); + + m_partCharset.Assign(Substring(line, start, end - start)); + } + + if (StringBeginsWith(lowerCaseLine, "content-transfer-encoding:"_ns) && + lowerCaseLine.LowerCaseFindASCII(ENCODING_BASE64) != kNotFound) + m_base64part = true; +} + +/** + * Decodes the given base64 string. + * + * It returns its decoded string in its input. + * + * @param pBufInOut (inout) a buffer of the string + */ +void nsMsgBodyHandler::Base64Decode(nsCString& pBufInOut) { + char* decodedBody = + PL_Base64Decode(pBufInOut.get(), pBufInOut.Length(), nullptr); + if (decodedBody) { + // Replace CR LF with spaces. + char* q = decodedBody; + while (*q) { + if (*q == '\n' || *q == '\r') *q = ' '; + q++; + } + pBufInOut.Adopt(decodedBody); + } +} diff --git a/comm/mailnews/search/src/nsMsgFilter.cpp b/comm/mailnews/search/src/nsMsgFilter.cpp new file mode 100644 index 0000000000..273b74aa07 --- /dev/null +++ b/comm/mailnews/search/src/nsMsgFilter.cpp @@ -0,0 +1,864 @@ +/* -*- Mode: C++; 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/. */ + +// this file implements the nsMsgFilter interface + +#include "msgCore.h" +#include "nsIMsgHdr.h" +#include "nsMsgFilterList.h" // for kFileVersion +#include "nsMsgFilter.h" +#include "nsMsgUtils.h" +#include "nsMsgLocalSearch.h" +#include "nsMsgSearchTerm.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgIncomingServer.h" +#include "nsMsgSearchValue.h" +#include "nsMsgI18N.h" +#include "nsNativeCharsetUtils.h" +#include "nsIOutputStream.h" +#include "nsIStringBundle.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsIMsgFilterService.h" +#include "nsIMsgNewsFolder.h" +#include "prmem.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Components.h" +#include "mozilla/intl/AppDateTimeFormat.h" + +static const char* kImapPrefix = "//imap:"; +static const char* kWhitespace = "\b\t\r\n "; + +nsMsgRuleAction::nsMsgRuleAction() + : m_type(nsMsgFilterAction::None), + m_priority(nsMsgPriority::notSet), + m_junkScore(0) {} + +nsMsgRuleAction::~nsMsgRuleAction() {} + +NS_IMPL_ISUPPORTS(nsMsgRuleAction, nsIMsgRuleAction) + +NS_IMPL_GETSET(nsMsgRuleAction, Type, nsMsgRuleActionType, m_type) + +NS_IMETHODIMP nsMsgRuleAction::SetPriority(nsMsgPriorityValue aPriority) { + NS_ENSURE_TRUE(m_type == nsMsgFilterAction::ChangePriority, + NS_ERROR_ILLEGAL_VALUE); + m_priority = aPriority; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgRuleAction::GetPriority(nsMsgPriorityValue* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_TRUE(m_type == nsMsgFilterAction::ChangePriority, + NS_ERROR_ILLEGAL_VALUE); + *aResult = m_priority; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgRuleAction::SetTargetFolderUri(const nsACString& aUri) { + NS_ENSURE_TRUE(m_type == nsMsgFilterAction::MoveToFolder || + m_type == nsMsgFilterAction::CopyToFolder, + NS_ERROR_ILLEGAL_VALUE); + m_folderUri = aUri; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgRuleAction::GetTargetFolderUri(nsACString& aResult) { + NS_ENSURE_TRUE(m_type == nsMsgFilterAction::MoveToFolder || + m_type == nsMsgFilterAction::CopyToFolder, + NS_ERROR_ILLEGAL_VALUE); + aResult = m_folderUri; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgRuleAction::SetJunkScore(int32_t aJunkScore) { + NS_ENSURE_TRUE(m_type == nsMsgFilterAction::JunkScore && aJunkScore >= 0 && + aJunkScore <= 100, + NS_ERROR_ILLEGAL_VALUE); + m_junkScore = aJunkScore; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgRuleAction::GetJunkScore(int32_t* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_TRUE(m_type == nsMsgFilterAction::JunkScore, + NS_ERROR_ILLEGAL_VALUE); + *aResult = m_junkScore; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgRuleAction::SetStrValue(const nsACString& aStrValue) { + m_strValue = aStrValue; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgRuleAction::GetStrValue(nsACString& aStrValue) { + aStrValue = m_strValue; + return NS_OK; +} + +/* attribute ACString customId; */ +NS_IMETHODIMP nsMsgRuleAction::GetCustomId(nsACString& aCustomId) { + aCustomId = m_customId; + return NS_OK; +} + +NS_IMETHODIMP nsMsgRuleAction::SetCustomId(const nsACString& aCustomId) { + m_customId = aCustomId; + return NS_OK; +} + +// this can only be called after the customId is set +NS_IMETHODIMP nsMsgRuleAction::GetCustomAction( + nsIMsgFilterCustomAction** aCustomAction) { + NS_ENSURE_ARG_POINTER(aCustomAction); + if (!m_customAction) { + if (m_customId.IsEmpty()) return NS_ERROR_NOT_INITIALIZED; + nsresult rv; + nsCOMPtr<nsIMsgFilterService> filterService = + do_GetService("@mozilla.org/messenger/services/filters;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = filterService->GetCustomAction(m_customId, + getter_AddRefs(m_customAction)); + NS_ENSURE_SUCCESS(rv, rv); + } + + // found the correct custom action + NS_ADDREF(*aCustomAction = m_customAction); + return NS_OK; +} + +nsMsgFilter::nsMsgFilter() + : m_type(nsMsgFilterType::InboxRule | nsMsgFilterType::Manual), + m_enabled(false), + m_temporary(false), + m_unparseable(false), + m_filterList(nullptr), + m_expressionTree(nullptr) {} + +nsMsgFilter::~nsMsgFilter() { delete m_expressionTree; } + +NS_IMPL_ISUPPORTS(nsMsgFilter, nsIMsgFilter) + +NS_IMPL_GETSET(nsMsgFilter, FilterType, nsMsgFilterTypeType, m_type) +NS_IMPL_GETSET(nsMsgFilter, Enabled, bool, m_enabled) +NS_IMPL_GETSET(nsMsgFilter, Temporary, bool, m_temporary) +NS_IMPL_GETSET(nsMsgFilter, Unparseable, bool, m_unparseable) + +NS_IMETHODIMP nsMsgFilter::GetFilterName(nsAString& name) { + name = m_filterName; + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::SetFilterName(const nsAString& name) { + m_filterName.Assign(name); + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::GetFilterDesc(nsACString& description) { + description = m_description; + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::SetFilterDesc(const nsACString& description) { + m_description.Assign(description); + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::GetUnparsedBuffer(nsACString& unparsedBuffer) { + unparsedBuffer = m_unparsedBuffer; + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::SetUnparsedBuffer(const nsACString& unparsedBuffer) { + m_unparsedBuffer.Assign(unparsedBuffer); + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::AddTerm( + nsMsgSearchAttribValue attrib, /* attribute for this term */ + nsMsgSearchOpValue op, /* operator e.g. opContains */ + nsIMsgSearchValue* value, /* value e.g. "Dogbert" */ + bool BooleanAND, /* true if AND is the boolean operator. + false if OR is the boolean operators */ + const nsACString& arbitraryHeader) /* arbitrary header specified by user. + ignored unless attrib = attribOtherHeader */ +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::AppendTerm(nsIMsgSearchTerm* aTerm) { + NS_ENSURE_TRUE(aTerm, NS_ERROR_NULL_POINTER); + // invalidate expression tree if we're changing the terms + delete m_expressionTree; + m_expressionTree = nullptr; + m_termList.AppendElement(aTerm); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFilter::CreateTerm(nsIMsgSearchTerm** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + NS_ADDREF(*aResult = new nsMsgSearchTerm); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFilter::CreateAction(nsIMsgRuleAction** aAction) { + NS_ENSURE_ARG_POINTER(aAction); + NS_ADDREF(*aAction = new nsMsgRuleAction); + return NS_OK; +} + +// All the rules' actions form a unit, with no real order imposed. +// But certain actions like MoveToFolder or StopExecution would make us drop +// consecutive actions, while actions like AddTag implicitly care about the +// order of invocation. Hence we do as little reordering as possible, keeping +// the user-defined order as much as possible. +// We explicitly don't allow for filters which do "tag message as Important, +// copy it to another folder, tag it as To Do also, copy this different state +// elsewhere" in one go. You need to define separate filters for that. +// +// The order of actions returned by this method: +// index action(s) +// ------- --------- +// 0 FetchBodyFromPop3Server +// 1..n all other 'normal' actions, in their original order +// n+1..m CopyToFolder +// m+1 MoveToFolder or Delete +// m+2 StopExecution +NS_IMETHODIMP +nsMsgFilter::GetSortedActionList( + nsTArray<RefPtr<nsIMsgRuleAction>>& aActionList) { + aActionList.Clear(); + aActionList.SetCapacity(m_actionList.Length()); + + // hold separate pointers into the action list + uint32_t nextIndexForNormal = 0, nextIndexForCopy = 0, nextIndexForMove = 0; + for (auto action : m_actionList) { + if (!action) continue; + + nsMsgRuleActionType actionType; + action->GetType(&actionType); + switch (actionType) { + case nsMsgFilterAction::FetchBodyFromPop3Server: { + // always insert in front + aActionList.InsertElementAt(0, action); + ++nextIndexForNormal; + ++nextIndexForCopy; + ++nextIndexForMove; + break; + } + + case nsMsgFilterAction::CopyToFolder: { + // insert into copy actions block, in order of appearance + aActionList.InsertElementAt(nextIndexForCopy, action); + ++nextIndexForCopy; + ++nextIndexForMove; + break; + } + + case nsMsgFilterAction::MoveToFolder: + case nsMsgFilterAction::Delete: { + // insert into move/delete action block + aActionList.InsertElementAt(nextIndexForMove, action); + ++nextIndexForMove; + break; + } + + case nsMsgFilterAction::StopExecution: { + // insert into stop action block + aActionList.AppendElement(action); + break; + } + + default: { + // insert into normal action block, in order of appearance + aActionList.InsertElementAt(nextIndexForNormal, action); + ++nextIndexForNormal; + ++nextIndexForCopy; + ++nextIndexForMove; + break; + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFilter::AppendAction(nsIMsgRuleAction* aAction) { + NS_ENSURE_ARG_POINTER(aAction); + + m_actionList.AppendElement(aAction); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFilter::GetActionAt(uint32_t aIndex, nsIMsgRuleAction** aAction) { + NS_ENSURE_ARG_POINTER(aAction); + NS_ENSURE_ARG(aIndex < m_actionList.Length()); + + NS_ENSURE_TRUE(m_actionList[aIndex], NS_ERROR_ILLEGAL_VALUE); + NS_IF_ADDREF(*aAction = m_actionList[aIndex]); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFilter::GetActionIndex(nsIMsgRuleAction* aAction, int32_t* aIndex) { + NS_ENSURE_ARG_POINTER(aIndex); + + *aIndex = m_actionList.IndexOf(aAction); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFilter::GetActionCount(uint32_t* aCount) { + NS_ENSURE_ARG_POINTER(aCount); + + *aCount = m_actionList.Length(); + return NS_OK; +} + +NS_IMETHODIMP // for editing a filter +nsMsgFilter::ClearActionList() { + m_actionList.Clear(); + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::GetTerm( + int32_t termIndex, + nsMsgSearchAttribValue* attrib, /* attribute for this term */ + nsMsgSearchOpValue* op, /* operator e.g. opContains */ + nsIMsgSearchValue** value, /* value e.g. "Dogbert" */ + bool* booleanAnd, /* true if AND is the boolean operator. false if OR is the + boolean operator */ + nsACString& arbitraryHeader) /* arbitrary header specified by user.ignore + unless attrib = attribOtherHeader */ +{ + if (termIndex >= (int32_t)m_termList.Length()) { + return NS_ERROR_INVALID_ARG; + } + nsIMsgSearchTerm* term = m_termList[termIndex]; + if (attrib) term->GetAttrib(attrib); + if (op) term->GetOp(op); + if (value) term->GetValue(value); + if (booleanAnd) term->GetBooleanAnd(booleanAnd); + if (attrib && *attrib > nsMsgSearchAttrib::OtherHeader && + *attrib < nsMsgSearchAttrib::kNumMsgSearchAttributes) { + term->GetArbitraryHeader(arbitraryHeader); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::GetSearchTerms( + nsTArray<RefPtr<nsIMsgSearchTerm>>& terms) { + delete m_expressionTree; + m_expressionTree = nullptr; + terms = m_termList.Clone(); + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::SetSearchTerms( + nsTArray<RefPtr<nsIMsgSearchTerm>> const& terms) { + delete m_expressionTree; + m_expressionTree = nullptr; + m_termList = terms.Clone(); + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::SetScope(nsIMsgSearchScopeTerm* aResult) { + m_scope = aResult; + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilter::GetScope(nsIMsgSearchScopeTerm** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + NS_IF_ADDREF(*aResult = m_scope); + return NS_OK; +} + +// This function handles the logging both for success of filtering +// (NS_SUCCEEDED(aRcode)), and for error reporting (NS_FAILED(aRcode) +// when the filter action (such as file move/copy) failed. +// +// @param aRcode NS_OK for successful filtering +// operation, otherwise, an error code for filtering failure. +// @param aErrmsg Not used for success case (ignored), and a non-null +// error message for failure case. +// +// CAUTION: Unless logging is enabled, no error/warning is shown. +// So enable logging if you would like to see the error/warning. +// +// XXX The current code in this file does not report errors of minor +// operations such as adding labels and so forth which may fail when +// underlying file system for the message store experiences +// failure. For now, most visible major errors such as message +// move/copy failures are taken care of. +// +// XXX Possible Improvement: For error case reporting, someone might +// want to implement a transient message that appears and stick until +// the user clears in the message status bar, etc. For now, we log an +// error in a similar form as a conventional successful filter event +// with additional error information at the beginning. +// +nsresult nsMsgFilter::LogRuleHitGeneric(nsIMsgRuleAction* aFilterAction, + nsIMsgDBHdr* aMsgHdr, nsresult aRcode, + const nsACString& aErrmsg) { + NS_ENSURE_ARG_POINTER(aFilterAction); + NS_ENSURE_ARG_POINTER(aMsgHdr); + + NS_ENSURE_TRUE(m_filterList, NS_OK); + + PRTime date; + nsMsgRuleActionType actionType; + + nsString authorValue; + nsString subjectValue; + nsString filterName; + nsString dateValue; + + GetFilterName(filterName); + aFilterAction->GetType(&actionType); + (void)aMsgHdr->GetDate(&date); + PRExplodedTime exploded; + PR_ExplodeTime(date, PR_LocalTimeParameters, &exploded); + + mozilla::intl::DateTimeFormat::StyleBag style; + style.date = mozilla::Some(mozilla::intl::DateTimeFormat::Style::Short); + style.time = mozilla::Some(mozilla::intl::DateTimeFormat::Style::Long); + mozilla::intl::AppDateTimeFormat::Format(style, &exploded, dateValue); + + (void)aMsgHdr->GetMime2DecodedAuthor(authorValue); + (void)aMsgHdr->GetMime2DecodedSubject(subjectValue); + + nsString buffer; + // this is big enough to hold a log entry. + // do this so we avoid growing and copying as we append to the log. + buffer.SetCapacity(512); + + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::components::StringBundle::Service(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIStringBundle> bundle; + nsresult rv = bundleService->CreateBundle( + "chrome://messenger/locale/filter.properties", getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + // If error, prefix with the error code and error message. + // A desired wording (without NEWLINEs): + // Filter Action Failed "Move failed" with error code=0x80004005 + // while attempting: Applied filter "test" to message from + // Some Test <test@example.com> - send test 3 at 2/13/2015 11:32:53 AM + // moved message id = 54DE5165.7000907@example.com to + // mailbox://nobody@Local%20Folders/test + if (NS_FAILED(aRcode)) { + // Convert aErrmsg to UTF16 string, and + // convert aRcode to UTF16 string in advance. + char tcode[20]; + PR_snprintf(tcode, sizeof(tcode), "0x%08x", aRcode); + NS_ConvertASCIItoUTF16 tcode16(tcode); + + nsString tErrmsg; + if (actionType != nsMsgFilterAction::Custom) { + // If this is one of our internal actions, the passed string + // is an identifier to get from the bundle. + rv = + bundle->GetStringFromName(PromiseFlatCString(aErrmsg).get(), tErrmsg); + if (NS_FAILED(rv)) tErrmsg.Assign(NS_ConvertUTF8toUTF16(aErrmsg)); + } else { + // The addon creating the custom action should have passed a localized + // string. + tErrmsg.Assign(NS_ConvertUTF8toUTF16(aErrmsg)); + } + AutoTArray<nsString, 2> logErrorFormatStrings = {tErrmsg, tcode16}; + + nsString filterFailureWarningPrefix; + rv = bundle->FormatStringFromName("filterFailureWarningPrefix", + logErrorFormatStrings, + filterFailureWarningPrefix); + NS_ENSURE_SUCCESS(rv, rv); + buffer += filterFailureWarningPrefix; + buffer.AppendLiteral("\n"); + } + + AutoTArray<nsString, 4> filterLogDetectFormatStrings = { + filterName, authorValue, subjectValue, dateValue}; + nsString filterLogDetectStr; + rv = bundle->FormatStringFromName( + "filterLogDetectStr", filterLogDetectFormatStrings, filterLogDetectStr); + NS_ENSURE_SUCCESS(rv, rv); + + buffer += filterLogDetectStr; + buffer.AppendLiteral("\n"); + + if (actionType == nsMsgFilterAction::MoveToFolder || + actionType == nsMsgFilterAction::CopyToFolder) { + nsCString actionFolderUri; + aFilterAction->GetTargetFolderUri(actionFolderUri); + + nsCString msgId; + aMsgHdr->GetMessageId(getter_Copies(msgId)); + + AutoTArray<nsString, 2> logMoveFormatStrings; + CopyUTF8toUTF16(msgId, *logMoveFormatStrings.AppendElement()); + CopyUTF8toUTF16(actionFolderUri, *logMoveFormatStrings.AppendElement()); + nsString logMoveStr; + rv = bundle->FormatStringFromName( + (actionType == nsMsgFilterAction::MoveToFolder) ? "logMoveStr" + : "logCopyStr", + logMoveFormatStrings, logMoveStr); + NS_ENSURE_SUCCESS(rv, rv); + + buffer += logMoveStr; + } else if (actionType == nsMsgFilterAction::Custom) { + nsCOMPtr<nsIMsgFilterCustomAction> customAction; + nsAutoString filterActionName; + rv = aFilterAction->GetCustomAction(getter_AddRefs(customAction)); + if (NS_SUCCEEDED(rv) && customAction) + customAction->GetName(filterActionName); + if (filterActionName.IsEmpty()) + bundle->GetStringFromName("filterMissingCustomAction", filterActionName); + buffer += filterActionName; + } else { + nsString actionValue; + nsAutoCString filterActionID; + filterActionID = "filterAction"_ns; + filterActionID.AppendInt(actionType); + rv = bundle->GetStringFromName(filterActionID.get(), actionValue); + NS_ENSURE_SUCCESS(rv, rv); + + buffer += actionValue; + } + buffer.AppendLiteral("\n"); + + return m_filterList->LogFilterMessage(buffer, nullptr); +} + +NS_IMETHODIMP nsMsgFilter::LogRuleHit(nsIMsgRuleAction* aFilterAction, + nsIMsgDBHdr* aMsgHdr) { + return nsMsgFilter::LogRuleHitGeneric(aFilterAction, aMsgHdr, NS_OK, + EmptyCString()); +} + +NS_IMETHODIMP nsMsgFilter::LogRuleHitFail(nsIMsgRuleAction* aFilterAction, + nsIMsgDBHdr* aMsgHdr, nsresult aRcode, + const nsACString& aErrMsg) { + return nsMsgFilter::LogRuleHitGeneric(aFilterAction, aMsgHdr, aRcode, + aErrMsg); +} + +NS_IMETHODIMP +nsMsgFilter::MatchHdr(nsIMsgDBHdr* msgHdr, nsIMsgFolder* folder, + nsIMsgDatabase* db, const nsACString& headers, + bool* pResult) { + NS_ENSURE_ARG_POINTER(folder); + NS_ENSURE_ARG_POINTER(msgHdr); + nsCString folderCharset = "UTF-8"_ns; + nsCOMPtr<nsIMsgNewsFolder> newsfolder(do_QueryInterface(folder)); + if (newsfolder) newsfolder->GetCharset(folderCharset); + return nsMsgSearchOfflineMail::MatchTermsForFilter( + msgHdr, m_termList, folderCharset.get(), m_scope, db, headers, + &m_expressionTree, pResult); +} + +NS_IMETHODIMP +nsMsgFilter::SetFilterList(nsIMsgFilterList* filterList) { + // doesn't hold a ref. + m_filterList = filterList; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFilter::GetFilterList(nsIMsgFilterList** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + NS_IF_ADDREF(*aResult = m_filterList); + return NS_OK; +} + +void nsMsgFilter::SetFilterScript(nsCString* fileName) { + m_scriptFileName = *fileName; +} + +nsresult nsMsgFilter::ConvertMoveOrCopyToFolderValue( + nsIMsgRuleAction* filterAction, nsCString& moveValue) { + NS_ENSURE_ARG_POINTER(filterAction); + int16_t filterVersion = kFileVersion; + if (m_filterList) m_filterList->GetVersion(&filterVersion); + if (filterVersion <= k60Beta1Version) { + nsCOMPtr<nsIMsgFolder> rootFolder; + nsCString folderUri; + + m_filterList->GetFolder(getter_AddRefs(rootFolder)); + // if relative path starts with kImap, this is a move to folder on the same + // server + if (moveValue.Find(kImapPrefix) == 0) { + int32_t prefixLen = PL_strlen(kImapPrefix); + nsAutoCString originalServerPath(Substring(moveValue, prefixLen)); + if (filterVersion == k45Version) { + nsAutoString unicodeStr; + NS_CopyNativeToUnicode(originalServerPath, unicodeStr); + + nsresult rv = CopyUTF16toMUTF7(unicodeStr, originalServerPath); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr<nsIMsgFolder> destIFolder; + if (rootFolder) { + rootFolder->FindSubFolder(originalServerPath, + getter_AddRefs(destIFolder)); + if (destIFolder) { + destIFolder->GetURI(folderUri); + filterAction->SetTargetFolderUri(folderUri); + moveValue.Assign(folderUri); + } + } + } else { + // start off leaving the value the same. + filterAction->SetTargetFolderUri(moveValue); + nsresult rv = NS_OK; + nsCOMPtr<nsIMsgFolder> localMailRoot; + rootFolder->GetURI(folderUri); + // if the root folder is not imap, than the local mail root is the server + // root. otherwise, it's the migrated local folders. + if (!StringBeginsWith(folderUri, "imap:"_ns)) + localMailRoot = rootFolder; + else { + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgIncomingServer> server; + rv = accountManager->GetLocalFoldersServer(getter_AddRefs(server)); + if (NS_SUCCEEDED(rv) && server) + rv = server->GetRootFolder(getter_AddRefs(localMailRoot)); + } + if (NS_SUCCEEDED(rv) && localMailRoot) { + nsCString localRootURI; + nsCOMPtr<nsIMsgFolder> destIMsgFolder; + localMailRoot->GetURI(localRootURI); + nsCString destFolderUri; + destFolderUri.Assign(localRootURI); + // need to remove ".sbd" from moveValue, and perhaps escape it. + int32_t offset = moveValue.Find(FOLDER_SUFFIX8 "/"); + if (offset != -1) moveValue.Cut(offset, FOLDER_SUFFIX_LENGTH); + +#ifdef XP_MACOSX + nsCString unescapedMoveValue; + MsgUnescapeString(moveValue, 0, unescapedMoveValue); + moveValue = unescapedMoveValue; +#endif + destFolderUri.Append('/'); + if (filterVersion == k45Version) { + nsAutoString unicodeStr; + NS_CopyNativeToUnicode(moveValue, unicodeStr); + rv = NS_MsgEscapeEncodeURLPath(unicodeStr, moveValue); + } + destFolderUri.Append(moveValue); + localMailRoot->GetChildWithURI(destFolderUri, true, + false /*caseInsensitive*/, + getter_AddRefs(destIMsgFolder)); + + if (destIMsgFolder) { + destIMsgFolder->GetURI(folderUri); + filterAction->SetTargetFolderUri(folderUri); + moveValue.Assign(folderUri); + } + } + } + } else + filterAction->SetTargetFolderUri(moveValue); + + return NS_OK; + // set m_action.m_value.m_folderUri +} + +NS_IMETHODIMP +nsMsgFilter::SaveToTextFile(nsIOutputStream* aStream) { + NS_ENSURE_ARG_POINTER(aStream); + if (m_unparseable) { + uint32_t bytesWritten; + // we need to trim leading whitespaces before filing out + m_unparsedBuffer.Trim(kWhitespace, true /*leadingCharacters*/, + false /*trailingCharacters*/); + return aStream->Write(m_unparsedBuffer.get(), m_unparsedBuffer.Length(), + &bytesWritten); + } + nsresult err = m_filterList->WriteWstrAttr(nsIMsgFilterList::attribName, + m_filterName.get(), aStream); + err = m_filterList->WriteBoolAttr(nsIMsgFilterList::attribEnabled, m_enabled, + aStream); + err = m_filterList->WriteStrAttr(nsIMsgFilterList::attribDescription, + m_description.get(), aStream); + err = + m_filterList->WriteIntAttr(nsIMsgFilterList::attribType, m_type, aStream); + if (IsScript()) + err = m_filterList->WriteStrAttr(nsIMsgFilterList::attribScriptFile, + m_scriptFileName.get(), aStream); + else + err = SaveRule(aStream); + return err; +} + +nsresult nsMsgFilter::SaveRule(nsIOutputStream* aStream) { + nsresult err = NS_OK; + nsCOMPtr<nsIMsgFilterList> filterList; + GetFilterList(getter_AddRefs(filterList)); + nsAutoCString actionFilingStr; + + uint32_t numActions; + err = GetActionCount(&numActions); + NS_ENSURE_SUCCESS(err, err); + + for (uint32_t index = 0; index < numActions; index++) { + nsCOMPtr<nsIMsgRuleAction> action; + err = GetActionAt(index, getter_AddRefs(action)); + if (NS_FAILED(err) || !action) continue; + + nsMsgRuleActionType actionType; + action->GetType(&actionType); + GetActionFilingStr(actionType, actionFilingStr); + + err = filterList->WriteStrAttr(nsIMsgFilterList::attribAction, + actionFilingStr.get(), aStream); + NS_ENSURE_SUCCESS(err, err); + + switch (actionType) { + case nsMsgFilterAction::MoveToFolder: + case nsMsgFilterAction::CopyToFolder: { + nsCString imapTargetString; + action->GetTargetFolderUri(imapTargetString); + err = filterList->WriteStrAttr(nsIMsgFilterList::attribActionValue, + imapTargetString.get(), aStream); + } break; + case nsMsgFilterAction::ChangePriority: { + nsMsgPriorityValue priorityValue; + action->GetPriority(&priorityValue); + nsAutoCString priority; + NS_MsgGetUntranslatedPriorityName(priorityValue, priority); + err = filterList->WriteStrAttr(nsIMsgFilterList::attribActionValue, + priority.get(), aStream); + } break; + case nsMsgFilterAction::JunkScore: { + int32_t junkScore; + action->GetJunkScore(&junkScore); + err = filterList->WriteIntAttr(nsIMsgFilterList::attribActionValue, + junkScore, aStream); + } break; + case nsMsgFilterAction::AddTag: + case nsMsgFilterAction::Reply: + case nsMsgFilterAction::Forward: { + nsCString strValue; + action->GetStrValue(strValue); + // strValue is e-mail address + err = filterList->WriteStrAttr(nsIMsgFilterList::attribActionValue, + strValue.get(), aStream); + } break; + case nsMsgFilterAction::Custom: { + nsAutoCString id; + action->GetCustomId(id); + err = filterList->WriteStrAttr(nsIMsgFilterList::attribCustomId, + id.get(), aStream); + nsAutoCString strValue; + action->GetStrValue(strValue); + if (strValue.Length()) + err = filterList->WriteWstrAttr(nsIMsgFilterList::attribActionValue, + NS_ConvertUTF8toUTF16(strValue).get(), + aStream); + } break; + + default: + break; + } + NS_ENSURE_SUCCESS(err, err); + } + // and here the fun begins - file out term list... + nsAutoCString condition; + err = MsgTermListToString(m_termList, condition); + NS_ENSURE_SUCCESS(err, err); + return filterList->WriteStrAttr(nsIMsgFilterList::attribCondition, + condition.get(), aStream); +} + +// for each action, this table encodes the filterTypes that support the action. +struct RuleActionsTableEntry { + nsMsgRuleActionType action; + const char* + actionFilingStr; /* used for filing out filters, don't translate! */ +}; + +static struct RuleActionsTableEntry ruleActionsTable[] = { + {nsMsgFilterAction::MoveToFolder, "Move to folder"}, + {nsMsgFilterAction::CopyToFolder, "Copy to folder"}, + {nsMsgFilterAction::ChangePriority, "Change priority"}, + {nsMsgFilterAction::Delete, "Delete"}, + {nsMsgFilterAction::MarkRead, "Mark read"}, + {nsMsgFilterAction::KillThread, "Ignore thread"}, + {nsMsgFilterAction::KillSubthread, "Ignore subthread"}, + {nsMsgFilterAction::WatchThread, "Watch thread"}, + {nsMsgFilterAction::MarkFlagged, "Mark flagged"}, + {nsMsgFilterAction::Reply, "Reply"}, + {nsMsgFilterAction::Forward, "Forward"}, + {nsMsgFilterAction::StopExecution, "Stop execution"}, + {nsMsgFilterAction::DeleteFromPop3Server, "Delete from Pop3 server"}, + {nsMsgFilterAction::LeaveOnPop3Server, "Leave on Pop3 server"}, + {nsMsgFilterAction::JunkScore, "JunkScore"}, + {nsMsgFilterAction::FetchBodyFromPop3Server, "Fetch body from Pop3Server"}, + {nsMsgFilterAction::AddTag, "AddTag"}, + {nsMsgFilterAction::MarkUnread, "Mark unread"}, + {nsMsgFilterAction::Custom, "Custom"}, +}; + +static const unsigned int sNumActions = MOZ_ARRAY_LENGTH(ruleActionsTable); + +const char* nsMsgFilter::GetActionStr(nsMsgRuleActionType action) { + for (unsigned int i = 0; i < sNumActions; i++) { + if (action == ruleActionsTable[i].action) + return ruleActionsTable[i].actionFilingStr; + } + return ""; +} +/*static */ nsresult nsMsgFilter::GetActionFilingStr(nsMsgRuleActionType action, + nsCString& actionStr) { + for (unsigned int i = 0; i < sNumActions; i++) { + if (action == ruleActionsTable[i].action) { + actionStr = ruleActionsTable[i].actionFilingStr; + return NS_OK; + } + } + return NS_ERROR_INVALID_ARG; +} + +nsMsgRuleActionType nsMsgFilter::GetActionForFilingStr(nsCString& actionStr) { + for (unsigned int i = 0; i < sNumActions; i++) { + if (actionStr.Equals(ruleActionsTable[i].actionFilingStr)) + return ruleActionsTable[i].action; + } + return nsMsgFilterAction::None; +} + +int16_t nsMsgFilter::GetVersion() { + if (!m_filterList) return 0; + int16_t version; + m_filterList->GetVersion(&version); + return version; +} + +#ifdef DEBUG +void nsMsgFilter::Dump() { + nsAutoCString s; + LossyCopyUTF16toASCII(m_filterName, s); + printf("filter %s type = %c desc = %s\n", s.get(), m_type + '0', + m_description.get()); +} +#endif diff --git a/comm/mailnews/search/src/nsMsgFilter.h b/comm/mailnews/search/src/nsMsgFilter.h new file mode 100644 index 0000000000..bf77f7a992 --- /dev/null +++ b/comm/mailnews/search/src/nsMsgFilter.h @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef _nsMsgFilter_H_ +#define _nsMsgFilter_H_ + +#include "nscore.h" +#include "nsISupports.h" +#include "nsIMsgFilter.h" +#include "nsIMsgSearchScopeTerm.h" +#include "nsMsgSearchBoolExpression.h" +#include "nsIMsgFilterCustomAction.h" + +class nsMsgRuleAction : public nsIMsgRuleAction { + public: + NS_DECL_ISUPPORTS + + nsMsgRuleAction(); + + NS_DECL_NSIMSGRULEACTION + + private: + virtual ~nsMsgRuleAction(); + + nsMsgRuleActionType m_type; + // this used to be a union - why bother? + nsMsgPriorityValue m_priority; /* priority to set rule to */ + nsCString m_folderUri; + int32_t m_junkScore; /* junk score (or arbitrary int value?) */ + // arbitrary string value. Currently, email address to forward to + nsCString m_strValue; + nsCString m_customId; + nsCOMPtr<nsIMsgFilterCustomAction> m_customAction; +}; + +class nsMsgFilter : public nsIMsgFilter { + public: + NS_DECL_ISUPPORTS + + nsMsgFilter(); + + NS_DECL_NSIMSGFILTER + + nsMsgFilterTypeType GetType() { return m_type; } + void SetType(nsMsgFilterTypeType type) { m_type = type; } + bool GetEnabled() { return m_enabled; } + void SetFilterScript(nsCString* filterName); + + bool IsScript() { + return (m_type & (nsMsgFilterType::InboxJavaScript | + nsMsgFilterType::NewsJavaScript)) != 0; + } + + // filing routines. + nsresult SaveRule(nsIOutputStream* aStream); + + int16_t GetVersion(); +#ifdef DEBUG + void Dump(); +#endif + + nsresult ConvertMoveOrCopyToFolderValue(nsIMsgRuleAction* filterAction, + nsCString& relativePath); + static const char* GetActionStr(nsMsgRuleActionType action); + static nsresult GetActionFilingStr(nsMsgRuleActionType action, + nsCString& actionStr); + static nsMsgRuleActionType GetActionForFilingStr(nsCString& actionStr); + + protected: + /* + * Reporting function for filtering success/failure. + * Logging has to be enabled for the message to appear. + */ + nsresult LogRuleHitGeneric(nsIMsgRuleAction* aFilterAction, + nsIMsgDBHdr* aMsgHdr, nsresult aRcode, + const nsACString& aErrmsg); + + virtual ~nsMsgFilter(); + + nsMsgFilterTypeType m_type; + nsString m_filterName; + nsCString m_scriptFileName; // iff this filter is a script. + nsCString m_description; + nsCString m_unparsedBuffer; + + bool m_enabled; + bool m_temporary; + bool m_unparseable; + nsIMsgFilterList* m_filterList; /* owning filter list */ + nsTArray<RefPtr<nsIMsgSearchTerm>> m_termList; /* criteria terms */ + nsCOMPtr<nsIMsgSearchScopeTerm> + m_scope; /* default for mail rules is inbox, but news rules could + have a newsgroup - LDAP would be invalid */ + nsTArray<nsCOMPtr<nsIMsgRuleAction>> m_actionList; + nsMsgSearchBoolExpression* m_expressionTree; +}; + +#endif diff --git a/comm/mailnews/search/src/nsMsgFilterList.cpp b/comm/mailnews/search/src/nsMsgFilterList.cpp new file mode 100644 index 0000000000..4a81eafe09 --- /dev/null +++ b/comm/mailnews/search/src/nsMsgFilterList.cpp @@ -0,0 +1,1207 @@ +/* -*- 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/. */ + +// this file implements the nsMsgFilterList interface + +#include "nsTextFormatter.h" + +#include "msgCore.h" +#include "nsMsgFilterList.h" +#include "nsMsgFilter.h" +#include "nsIMsgHdr.h" +#include "nsIMsgFilterHitNotify.h" +#include "nsMsgUtils.h" +#include "nsMsgSearchTerm.h" +#include "nsString.h" +#include "nsIMsgFilterService.h" +#include "nsMsgSearchScopeTerm.h" +#include "nsIStringBundle.h" +#include "nsNetUtil.h" +#include "nsIInputStream.h" +#include "nsNativeCharsetUtils.h" +#include "nsMemory.h" +#include "prmem.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Components.h" +#include "mozilla/Logging.h" +#include "mozilla/intl/AppDateTimeFormat.h" +#include <ctype.h> + +// Marker for EOF or failure during read +#define EOF_CHAR -1 + +using namespace mozilla; + +extern LazyLogModule FILTERLOGMODULE; + +static uint32_t nextListId = 0; + +nsMsgFilterList::nsMsgFilterList() : m_fileVersion(0) { + m_loggingEnabled = false; + m_startWritingToBuffer = false; + m_temporaryList = false; + m_curFilter = nullptr; + m_listId.Assign("List"); + m_listId.AppendInt(nextListId++); + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("Creating a new filter list with id=%s", m_listId.get())); +} + +NS_IMPL_ADDREF(nsMsgFilterList) +NS_IMPL_RELEASE(nsMsgFilterList) +NS_IMPL_QUERY_INTERFACE(nsMsgFilterList, nsIMsgFilterList) + +NS_IMETHODIMP nsMsgFilterList::CreateFilter(const nsAString& name, + class nsIMsgFilter** aFilter) { + NS_ENSURE_ARG_POINTER(aFilter); + + NS_ADDREF(*aFilter = new nsMsgFilter); + + (*aFilter)->SetFilterName(name); + (*aFilter)->SetFilterList(this); + + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilterList::SetLoggingEnabled(bool enabled) { + if (!enabled) { + // Disabling logging has side effect of closing logfile (if open). + SetLogStream(nullptr); + } + m_loggingEnabled = enabled; + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilterList::GetLoggingEnabled(bool* enabled) { + *enabled = m_loggingEnabled; + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilterList::GetListId(nsACString& aListId) { + aListId.Assign(m_listId); + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilterList::GetFolder(nsIMsgFolder** aFolder) { + NS_ENSURE_ARG_POINTER(aFolder); + + NS_IF_ADDREF(*aFolder = m_folder); + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilterList::SetFolder(nsIMsgFolder* aFolder) { + m_folder = aFolder; + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilterList::SaveToFile(nsIOutputStream* stream) { + if (!stream) return NS_ERROR_NULL_POINTER; + return SaveTextFilters(stream); +} + +#define LOG_HEADER \ + "<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"UTF-8\">\n<style " \ + "type=\"text/css\">body{font-family:Consolas,\"Lucida " \ + "Console\",Monaco,\"Courier " \ + "New\",Courier,monospace;font-size:small}</style>\n</head>\n<body>\n" +#define LOG_HEADER_LEN (strlen(LOG_HEADER)) + +nsresult nsMsgFilterList::EnsureLogFile(nsIFile* file) { + bool exists; + nsresult rv = file->Exists(&exists); + if (NS_SUCCEEDED(rv) && !exists) { + rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 0666); + NS_ENSURE_SUCCESS(rv, rv); + } + + int64_t fileSize; + rv = file->GetFileSize(&fileSize); + NS_ENSURE_SUCCESS(rv, rv); + + // write the header at the start + if (fileSize == 0) { + nsCOMPtr<nsIOutputStream> outputStream; + rv = MsgGetFileStream(file, getter_AddRefs(outputStream)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t writeCount; + rv = outputStream->Write(LOG_HEADER, LOG_HEADER_LEN, &writeCount); + NS_ASSERTION(writeCount == LOG_HEADER_LEN, + "failed to write out log header"); + NS_ENSURE_SUCCESS(rv, rv); + outputStream->Close(); + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilterList::ClearLog() { + bool loggingEnabled = m_loggingEnabled; + + // disable logging while clearing (and close logStream if open). + SetLoggingEnabled(false); + + nsCOMPtr<nsIFile> file; + if (NS_SUCCEEDED(GetLogFile(getter_AddRefs(file)))) { + file->Remove(false); + // Recreate the file, with just the html header. + EnsureLogFile(file); + } + + SetLoggingEnabled(loggingEnabled); + return NS_OK; +} + +nsresult nsMsgFilterList::GetLogFile(nsIFile** aFile) { + NS_ENSURE_ARG_POINTER(aFile); + + // XXX todo + // the path to the log file won't change + // should we cache it? + nsCOMPtr<nsIMsgIncomingServer> server; + nsresult rv = m_folder->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString type; + rv = server->GetType(type); + NS_ENSURE_SUCCESS(rv, rv); + + bool isServer = false; + rv = m_folder->GetIsServer(&isServer); + NS_ENSURE_SUCCESS(rv, rv); + + // for news folders (not servers), the filter file is + // mcom.test.dat + // where the summary file is + // mcom.test.msf + // since the log is an html file we make it + // mcom.test.htm + if (type.EqualsLiteral("nntp") && !isServer) { + nsCOMPtr<nsIFile> thisFolder; + rv = m_folder->GetFilePath(getter_AddRefs(thisFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> filterLogFile = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = filterLogFile->InitWithFile(thisFolder); + NS_ENSURE_SUCCESS(rv, rv); + + // NOTE: + // we don't we need to call NS_MsgHashIfNecessary() + // it's already been hashed, if necessary + nsAutoString filterLogName; + rv = filterLogFile->GetLeafName(filterLogName); + NS_ENSURE_SUCCESS(rv, rv); + + filterLogName.AppendLiteral(u".htm"); + + rv = filterLogFile->SetLeafName(filterLogName); + NS_ENSURE_SUCCESS(rv, rv); + + filterLogFile.forget(aFile); + } else { + rv = server->GetLocalPath(aFile); + NS_ENSURE_SUCCESS(rv, rv); + + rv = (*aFile)->AppendNative("filterlog.html"_ns); + NS_ENSURE_SUCCESS(rv, rv); + } + return EnsureLogFile(*aFile); +} + +NS_IMETHODIMP +nsMsgFilterList::GetLogURL(nsACString& aLogURL) { + nsCOMPtr<nsIFile> file; + nsresult rv = GetLogFile(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_GetURLSpecFromFile(file, aLogURL); + NS_ENSURE_SUCCESS(rv, rv); + + return !aLogURL.IsEmpty() ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsMsgFilterList::SetLogStream(nsIOutputStream* aLogStream) { + // if there is a log stream already, close it + if (m_logStream) { + m_logStream->Close(); // will flush + } + + m_logStream = aLogStream; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFilterList::GetLogStream(nsIOutputStream** aLogStream) { + NS_ENSURE_ARG_POINTER(aLogStream); + + if (!m_logStream && m_loggingEnabled) { + nsCOMPtr<nsIFile> logFile; + nsresult rv = GetLogFile(getter_AddRefs(logFile)); + if (NS_SUCCEEDED(rv)) { + // Make sure it exists and has it's initial header. + rv = EnsureLogFile(logFile); + if (NS_SUCCEEDED(rv)) { + // append to the end of the log file + rv = MsgNewBufferedFileOutputStream( + getter_AddRefs(m_logStream), logFile, + PR_CREATE_FILE | PR_WRONLY | PR_APPEND, 0666); + } + } + if (NS_FAILED(rv)) { + m_logStream = nullptr; + } + } + + // Always returns NS_OK. The stream can be null. + NS_IF_ADDREF(*aLogStream = m_logStream); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFilterList::ApplyFiltersToHdr(nsMsgFilterTypeType filterType, + nsIMsgDBHdr* msgHdr, nsIMsgFolder* folder, + nsIMsgDatabase* db, + const nsACString& headers, + nsIMsgFilterHitNotify* listener, + nsIMsgWindow* msgWindow) { + MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug, + ("(Auto) nsMsgFilterList::ApplyFiltersToHdr")); + if (!msgHdr) { + // Sometimes we get here with no header, so let's not crash on that + // later on. + MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug, + ("(Auto) Called with NULL message header, nothing to do")); + return NS_ERROR_NULL_POINTER; + } + + nsCOMPtr<nsIMsgFilter> filter; + uint32_t filterCount = 0; + nsresult rv = GetFilterCount(&filterCount); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<nsMsgSearchScopeTerm> scope = + new nsMsgSearchScopeTerm(nullptr, nsMsgSearchScope::offlineMail, folder); + + nsString folderName; + folder->GetName(folderName); + nsMsgKey msgKey; + msgHdr->GetMessageKey(&msgKey); + nsCString typeName; + nsCOMPtr<nsIMsgFilterService> filterService = + do_GetService("@mozilla.org/messenger/services/filters;1", &rv); + filterService->FilterTypeName(filterType, typeName); + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("(Auto) Filter run initiated, trigger=%s (%i)", typeName.get(), + filterType)); + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("(Auto) Running %" PRIu32 + " filters from %s on message with key %" PRIu32 " in folder '%s'", + filterCount, m_listId.get(), msgKeyToInt(msgKey), + NS_ConvertUTF16toUTF8(folderName).get())); + + for (uint32_t filterIndex = 0; filterIndex < filterCount; filterIndex++) { + if (NS_SUCCEEDED(GetFilterAt(filterIndex, getter_AddRefs(filter)))) { + bool isEnabled; + nsMsgFilterTypeType curFilterType; + + filter->GetEnabled(&isEnabled); + if (!isEnabled) { + // clang-format off + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("(Auto) Skipping disabled filter at index %" PRIu32, + filterIndex)); + // clang-format on + continue; + } + + nsString filterName; + filter->GetFilterName(filterName); + filter->GetFilterType(&curFilterType); + if (curFilterType & filterType) { + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("(Auto) Running filter %" PRIu32, filterIndex)); + MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug, + ("(Auto) Filter name: %s", + NS_ConvertUTF16toUTF8(filterName).get())); + + nsresult matchTermStatus = NS_OK; + bool result = false; + + filter->SetScope(scope); + matchTermStatus = + filter->MatchHdr(msgHdr, folder, db, headers, &result); + filter->SetScope(nullptr); + if (NS_SUCCEEDED(matchTermStatus) && result && listener) { + nsCString msgId; + msgHdr->GetMessageId(getter_Copies(msgId)); + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("(Auto) Filter matched message with key %" PRIu32, + msgKeyToInt(msgKey))); + MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug, + ("(Auto) Matched message ID: %s", msgId.get())); + + bool applyMore = true; + rv = listener->ApplyFilterHit(filter, msgWindow, &applyMore); + if (NS_FAILED(rv)) { + MOZ_LOG(FILTERLOGMODULE, LogLevel::Error, + ("(Auto) Applying filter actions failed")); + LogFilterMessage(u"Applying filter actions failed"_ns, filter); + } else { + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("(Auto) Applying filter actions succeeded")); + } + if (NS_FAILED(rv) || !applyMore) { + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("(Auto) Stopping further filter execution" + " on this message")); + break; + } + } else { + if (NS_FAILED(matchTermStatus)) { + MOZ_LOG(FILTERLOGMODULE, LogLevel::Error, + ("(Auto) Filter evaluation failed")); + LogFilterMessage(u"Filter evaluation failed"_ns, filter); + } + if (!result) + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("(Auto) Filter didn't match")); + } + } else { + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("(Auto) Skipping filter of non-matching type" + " at index %" PRIu32, + filterIndex)); + } + } + } + if (NS_FAILED(rv)) { + // clang-format off + MOZ_LOG(FILTERLOGMODULE, LogLevel::Error, + ("(Auto) Filter run failed (%" PRIx32 ")", static_cast<uint32_t>(rv))); + // clang-format on + LogFilterMessage(u"Filter run failed"_ns, nullptr); + } + return rv; +} + +NS_IMETHODIMP +nsMsgFilterList::SetDefaultFile(nsIFile* aFile) { + m_defaultFile = aFile; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFilterList::GetDefaultFile(nsIFile** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + + NS_IF_ADDREF(*aResult = m_defaultFile); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFilterList::SaveToDefaultFile() { + nsresult rv; + nsCOMPtr<nsIMsgFilterService> filterService = + do_GetService("@mozilla.org/messenger/services/filters;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return filterService->SaveFilterList(this, m_defaultFile); +} + +typedef struct { + nsMsgFilterFileAttribValue attrib; + const char* attribName; +} FilterFileAttribEntry; + +static FilterFileAttribEntry FilterFileAttribTable[] = { + {nsIMsgFilterList::attribNone, ""}, + {nsIMsgFilterList::attribVersion, "version"}, + {nsIMsgFilterList::attribLogging, "logging"}, + {nsIMsgFilterList::attribName, "name"}, + {nsIMsgFilterList::attribEnabled, "enabled"}, + {nsIMsgFilterList::attribDescription, "description"}, + {nsIMsgFilterList::attribType, "type"}, + {nsIMsgFilterList::attribScriptFile, "scriptName"}, + {nsIMsgFilterList::attribAction, "action"}, + {nsIMsgFilterList::attribActionValue, "actionValue"}, + {nsIMsgFilterList::attribCondition, "condition"}, + {nsIMsgFilterList::attribCustomId, "customId"}, +}; + +static const unsigned int sNumFilterFileAttribTable = + MOZ_ARRAY_LENGTH(FilterFileAttribTable); + +// If we want to buffer file IO, wrap it in here. +int nsMsgFilterList::ReadChar(nsIInputStream* aStream) { + char newChar; + uint32_t bytesRead; + uint64_t bytesAvailable; + nsresult rv = aStream->Available(&bytesAvailable); + if (NS_FAILED(rv) || bytesAvailable == 0) return EOF_CHAR; + + rv = aStream->Read(&newChar, 1, &bytesRead); + if (NS_FAILED(rv) || !bytesRead) return EOF_CHAR; + + if (m_startWritingToBuffer) m_unparsedFilterBuffer.Append(newChar); + return (unsigned char)newChar; // Make sure the char is unsigned. +} + +int nsMsgFilterList::SkipWhitespace(nsIInputStream* aStream) { + int ch; + do { + ch = ReadChar(aStream); + } while (!(ch & 0x80) && + isspace(ch)); // isspace can crash with non-ascii input + + return ch; +} + +bool nsMsgFilterList::StrToBool(nsCString& str) { + return str.EqualsLiteral("yes"); +} + +int nsMsgFilterList::LoadAttrib(nsMsgFilterFileAttribValue& attrib, + nsIInputStream* aStream) { + char attribStr[100]; + int curChar; + attrib = nsIMsgFilterList::attribNone; + + curChar = SkipWhitespace(aStream); + int i; + for (i = 0; i + 1 < (int)(sizeof(attribStr));) { + if (curChar == EOF_CHAR || (!(curChar & 0x80) && isspace(curChar)) || + curChar == '=') + break; + attribStr[i++] = curChar; + curChar = ReadChar(aStream); + } + attribStr[i] = '\0'; + for (unsigned int tableIndex = 0; tableIndex < sNumFilterFileAttribTable; + tableIndex++) { + if (!PL_strcasecmp(attribStr, + FilterFileAttribTable[tableIndex].attribName)) { + attrib = FilterFileAttribTable[tableIndex].attrib; + break; + } + } + return curChar; +} + +const char* nsMsgFilterList::GetStringForAttrib( + nsMsgFilterFileAttribValue attrib) { + for (unsigned int tableIndex = 0; tableIndex < sNumFilterFileAttribTable; + tableIndex++) { + if (attrib == FilterFileAttribTable[tableIndex].attrib) + return FilterFileAttribTable[tableIndex].attribName; + } + return nullptr; +} + +nsresult nsMsgFilterList::LoadValue(nsCString& value, nsIInputStream* aStream) { + nsAutoCString valueStr; + int curChar; + value = ""; + curChar = SkipWhitespace(aStream); + if (curChar != '"') { + NS_ASSERTION(false, "expecting quote as start of value"); + return NS_MSG_FILTER_PARSE_ERROR; + } + curChar = ReadChar(aStream); + do { + if (curChar == '\\') { + int nextChar = ReadChar(aStream); + if (nextChar == '"') + curChar = '"'; + else if (nextChar == '\\') // replace "\\" with "\" + { + valueStr += curChar; + curChar = ReadChar(aStream); + } else { + valueStr += curChar; + curChar = nextChar; + } + } else { + if (curChar == EOF_CHAR || curChar == '"' || curChar == '\n' || + curChar == '\r') { + value += valueStr; + break; + } + } + valueStr += curChar; + curChar = ReadChar(aStream); + } while (curChar != EOF_CHAR); + return NS_OK; +} + +nsresult nsMsgFilterList::LoadTextFilters( + already_AddRefed<nsIInputStream> aStream) { + nsresult err = NS_OK; + uint64_t bytesAvailable; + + nsCOMPtr<nsIInputStream> bufStream; + nsCOMPtr<nsIInputStream> stream = std::move(aStream); + err = NS_NewBufferedInputStream(getter_AddRefs(bufStream), stream.forget(), + FILE_IO_BUFFER_SIZE); + NS_ENSURE_SUCCESS(err, err); + + nsMsgFilterFileAttribValue attrib; + nsCOMPtr<nsIMsgRuleAction> currentFilterAction; + // We'd really like to move lot's of these into the objects that they refer + // to. + do { + nsAutoCString value; + nsresult intToStringResult; + + int curChar; + curChar = LoadAttrib(attrib, bufStream); + if (curChar == EOF_CHAR) // reached eof + break; + err = LoadValue(value, bufStream); + if (NS_FAILED(err)) break; + + switch (attrib) { + case nsIMsgFilterList::attribNone: + if (m_curFilter) m_curFilter->SetUnparseable(true); + break; + case nsIMsgFilterList::attribVersion: + m_fileVersion = value.ToInteger(&intToStringResult); + if (NS_FAILED(intToStringResult)) { + attrib = nsIMsgFilterList::attribNone; + NS_ASSERTION(false, "error parsing filter file version"); + } + break; + case nsIMsgFilterList::attribLogging: + m_loggingEnabled = StrToBool(value); + // We are going to buffer each filter as we read them. + // Make sure no garbage is there + m_unparsedFilterBuffer.Truncate(); + m_startWritingToBuffer = true; // filters begin now + break; + case nsIMsgFilterList::attribName: // every filter starts w/ a name + { + if (m_curFilter) { + int32_t nextFilterStartPos = m_unparsedFilterBuffer.RFind("name"); + + nsAutoCString nextFilterPart; + nextFilterPart = Substring(m_unparsedFilterBuffer, nextFilterStartPos, + m_unparsedFilterBuffer.Length()); + m_unparsedFilterBuffer.SetLength(nextFilterStartPos); + + bool unparseableFilter; + m_curFilter->GetUnparseable(&unparseableFilter); + if (unparseableFilter) { + m_curFilter->SetUnparsedBuffer(m_unparsedFilterBuffer); + m_curFilter->SetEnabled(false); // disable the filter because we + // don't know how to apply it + } + m_unparsedFilterBuffer = nextFilterPart; + } + nsMsgFilter* filter = new nsMsgFilter; + if (filter == nullptr) { + err = NS_ERROR_OUT_OF_MEMORY; + break; + } + filter->SetFilterList(static_cast<nsIMsgFilterList*>(this)); + nsAutoString unicodeStr; + if (m_fileVersion == k45Version) { + NS_CopyNativeToUnicode(value, unicodeStr); + filter->SetFilterName(unicodeStr); + } else { + CopyUTF8toUTF16(value, unicodeStr); + filter->SetFilterName(unicodeStr); + } + m_curFilter = filter; + m_filters.AppendElement(filter); + } break; + case nsIMsgFilterList::attribEnabled: + if (m_curFilter) m_curFilter->SetEnabled(StrToBool(value)); + break; + case nsIMsgFilterList::attribDescription: + if (m_curFilter) m_curFilter->SetFilterDesc(value); + break; + case nsIMsgFilterList::attribType: + if (m_curFilter) { + // Older versions of filters didn't have the ability to turn on/off + // the manual filter context, so default manual to be on in that case + int32_t filterType = value.ToInteger(&intToStringResult); + if (m_fileVersion < kManualContextVersion) + filterType |= nsMsgFilterType::Manual; + m_curFilter->SetType((nsMsgFilterTypeType)filterType); + } + break; + case nsIMsgFilterList::attribScriptFile: + if (m_curFilter) m_curFilter->SetFilterScript(&value); + break; + case nsIMsgFilterList::attribAction: + if (m_curFilter) { + nsMsgRuleActionType actionType = + nsMsgFilter::GetActionForFilingStr(value); + if (actionType == nsMsgFilterAction::None) + m_curFilter->SetUnparseable(true); + else { + err = + m_curFilter->CreateAction(getter_AddRefs(currentFilterAction)); + NS_ENSURE_SUCCESS(err, err); + currentFilterAction->SetType(actionType); + m_curFilter->AppendAction(currentFilterAction); + } + } + break; + case nsIMsgFilterList::attribActionValue: + if (m_curFilter && currentFilterAction) { + nsMsgRuleActionType type; + currentFilterAction->GetType(&type); + if (type == nsMsgFilterAction::MoveToFolder || + type == nsMsgFilterAction::CopyToFolder) + err = m_curFilter->ConvertMoveOrCopyToFolderValue( + currentFilterAction, value); + else if (type == nsMsgFilterAction::ChangePriority) { + nsMsgPriorityValue outPriority; + nsresult res = + NS_MsgGetPriorityFromString(value.get(), outPriority); + if (NS_SUCCEEDED(res)) + currentFilterAction->SetPriority(outPriority); + else + NS_ASSERTION(false, "invalid priority in filter file"); + } else if (type == nsMsgFilterAction::JunkScore) { + nsresult res; + int32_t junkScore = value.ToInteger(&res); + if (NS_SUCCEEDED(res)) currentFilterAction->SetJunkScore(junkScore); + } else if (type == nsMsgFilterAction::Forward || + type == nsMsgFilterAction::Reply || + type == nsMsgFilterAction::AddTag || + type == nsMsgFilterAction::Custom) { + currentFilterAction->SetStrValue(value); + } + } + break; + case nsIMsgFilterList::attribCondition: + if (m_curFilter) { + if (m_fileVersion == k45Version) { + nsAutoString unicodeStr; + NS_CopyNativeToUnicode(value, unicodeStr); + CopyUTF16toUTF8(unicodeStr, value); + } + err = ParseCondition(m_curFilter, value.get()); + if (err == NS_ERROR_INVALID_ARG) + err = m_curFilter->SetUnparseable(true); + NS_ENSURE_SUCCESS(err, err); + } + break; + case nsIMsgFilterList::attribCustomId: + if (m_curFilter && currentFilterAction) { + err = currentFilterAction->SetCustomId(value); + NS_ENSURE_SUCCESS(err, err); + } + break; + } + } while (NS_SUCCEEDED(bufStream->Available(&bytesAvailable))); + + if (m_curFilter) { + bool unparseableFilter; + m_curFilter->GetUnparseable(&unparseableFilter); + if (unparseableFilter) { + m_curFilter->SetUnparsedBuffer(m_unparsedFilterBuffer); + m_curFilter->SetEnabled( + false); // disable the filter because we don't know how to apply it + } + } + + return err; +} + +// parse condition like "(subject, contains, fred) AND (body, isn't, "foo)")" +// values with close parens will be quoted. +// what about values with close parens and quotes? e.g., (body, isn't, "foo")") +// I guess interior quotes will need to be escaped - ("foo\")") +// which will get written out as (\"foo\\")\") and read in as ("foo\")" +// ALL means match all messages. +NS_IMETHODIMP nsMsgFilterList::ParseCondition(nsIMsgFilter* aFilter, + const char* aCondition) { + NS_ENSURE_ARG_POINTER(aFilter); + + bool done = false; + nsresult err = NS_OK; + const char* curPtr = aCondition; + if (!strcmp(aCondition, "ALL")) { + RefPtr<nsMsgSearchTerm> newTerm = new nsMsgSearchTerm; + newTerm->m_matchAll = true; + aFilter->AppendTerm(newTerm); + return NS_OK; + } + + while (!done) { + // insert code to save the boolean operator if there is one for this search + // term.... + const char* openParen = PL_strchr(curPtr, '('); + const char* orTermPos = PL_strchr( + curPtr, 'O'); // determine if an "OR" appears b4 the openParen... + bool ANDTerm = true; + if (orTermPos && + orTermPos < openParen) // make sure OR term falls before the '(' + ANDTerm = false; + + char* termDup = nullptr; + if (openParen) { + bool foundEndTerm = false; + bool inQuote = false; + for (curPtr = openParen + 1; *curPtr; curPtr++) { + if (*curPtr == '\\' && *(curPtr + 1) == '"') + curPtr++; + else if (*curPtr == ')' && !inQuote) { + foundEndTerm = true; + break; + } else if (*curPtr == '"') + inQuote = !inQuote; + } + if (foundEndTerm) { + int termLen = curPtr - openParen - 1; + termDup = (char*)PR_Malloc(termLen + 1); + if (termDup) { + PL_strncpy(termDup, openParen + 1, termLen + 1); + termDup[termLen] = '\0'; + } else { + err = NS_ERROR_OUT_OF_MEMORY; + break; + } + } + } else + break; + if (termDup) { + RefPtr<nsMsgSearchTerm> newTerm = new nsMsgSearchTerm; + // Invert nsMsgSearchTerm::EscapeQuotesInStr() + for (char *to = termDup, *from = termDup;;) { + if (*from == '\\' && from[1] == '"') from++; + if (!(*to++ = *from++)) break; + } + newTerm->m_booleanOp = (ANDTerm) ? nsMsgSearchBooleanOp::BooleanAND + : nsMsgSearchBooleanOp::BooleanOR; + + err = newTerm->DeStreamNew(termDup, PL_strlen(termDup)); + NS_ENSURE_SUCCESS(err, err); + aFilter->AppendTerm(newTerm); + PR_FREEIF(termDup); + } else + break; + } + return err; +} + +nsresult nsMsgFilterList::WriteIntAttr(nsMsgFilterFileAttribValue attrib, + int value, nsIOutputStream* aStream) { + nsresult rv = NS_OK; + const char* attribStr = GetStringForAttrib(attrib); + if (attribStr) { + uint32_t bytesWritten; + nsAutoCString writeStr(attribStr); + writeStr.AppendLiteral("=\""); + writeStr.AppendInt(value); + writeStr.AppendLiteral("\"" MSG_LINEBREAK); + rv = aStream->Write(writeStr.get(), writeStr.Length(), &bytesWritten); + } + return rv; +} + +NS_IMETHODIMP +nsMsgFilterList::WriteStrAttr(nsMsgFilterFileAttribValue attrib, + const char* aStr, nsIOutputStream* aStream) { + nsresult rv = NS_OK; + if (aStr && *aStr && + aStream) // only proceed if we actually have a string to write out. + { + char* escapedStr = nullptr; + if (PL_strchr(aStr, '"')) + escapedStr = nsMsgSearchTerm::EscapeQuotesInStr(aStr); + + const char* attribStr = GetStringForAttrib(attrib); + if (attribStr) { + uint32_t bytesWritten; + nsAutoCString writeStr(attribStr); + writeStr.AppendLiteral("=\""); + writeStr.Append((escapedStr) ? escapedStr : aStr); + writeStr.AppendLiteral("\"" MSG_LINEBREAK); + rv = aStream->Write(writeStr.get(), writeStr.Length(), &bytesWritten); + } + PR_Free(escapedStr); + } + return rv; +} + +nsresult nsMsgFilterList::WriteBoolAttr(nsMsgFilterFileAttribValue attrib, + bool boolVal, + nsIOutputStream* aStream) { + return WriteStrAttr(attrib, (boolVal) ? "yes" : "no", aStream); +} + +nsresult nsMsgFilterList::WriteWstrAttr(nsMsgFilterFileAttribValue attrib, + const char16_t* aFilterName, + nsIOutputStream* aStream) { + WriteStrAttr(attrib, NS_ConvertUTF16toUTF8(aFilterName).get(), aStream); + return NS_OK; +} + +nsresult nsMsgFilterList::SaveTextFilters(nsIOutputStream* aStream) { + uint32_t filterCount = 0; + nsresult err = GetFilterCount(&filterCount); + NS_ENSURE_SUCCESS(err, err); + + err = WriteIntAttr(nsIMsgFilterList::attribVersion, kFileVersion, aStream); + NS_ENSURE_SUCCESS(err, err); + err = + WriteBoolAttr(nsIMsgFilterList::attribLogging, m_loggingEnabled, aStream); + NS_ENSURE_SUCCESS(err, err); + for (uint32_t i = 0; i < filterCount; i++) { + nsCOMPtr<nsIMsgFilter> filter; + if (NS_SUCCEEDED(GetFilterAt(i, getter_AddRefs(filter))) && filter) { + filter->SetFilterList(this); + + // if the filter is temporary, don't write it to disk + bool isTemporary; + err = filter->GetTemporary(&isTemporary); + if (NS_SUCCEEDED(err) && !isTemporary) { + err = filter->SaveToTextFile(aStream); + if (NS_FAILED(err)) break; + } + } else + break; + } + if (NS_SUCCEEDED(err)) m_arbitraryHeaders.Truncate(); + return err; +} + +nsMsgFilterList::~nsMsgFilterList() { + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("Closing filter list %s", m_listId.get())); +} + +nsresult nsMsgFilterList::Close() { return NS_ERROR_NOT_IMPLEMENTED; } + +nsresult nsMsgFilterList::GetFilterCount(uint32_t* pCount) { + NS_ENSURE_ARG_POINTER(pCount); + + *pCount = m_filters.Length(); + return NS_OK; +} + +nsresult nsMsgFilterList::GetFilterAt(uint32_t filterIndex, + nsIMsgFilter** filter) { + NS_ENSURE_ARG_POINTER(filter); + + uint32_t filterCount = 0; + GetFilterCount(&filterCount); + NS_ENSURE_ARG(filterIndex < filterCount); + + NS_IF_ADDREF(*filter = m_filters[filterIndex]); + return NS_OK; +} + +nsresult nsMsgFilterList::GetFilterNamed(const nsAString& aName, + nsIMsgFilter** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + + uint32_t count = 0; + nsresult rv = GetFilterCount(&count); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = nullptr; + for (uint32_t i = 0; i < count; i++) { + nsCOMPtr<nsIMsgFilter> filter; + rv = GetFilterAt(i, getter_AddRefs(filter)); + if (NS_FAILED(rv)) continue; + + nsString filterName; + filter->GetFilterName(filterName); + if (filterName.Equals(aName)) { + filter.forget(aResult); + break; + } + } + + return NS_OK; +} + +nsresult nsMsgFilterList::SetFilterAt(uint32_t filterIndex, + nsIMsgFilter* filter) { + m_filters[filterIndex] = filter; + return NS_OK; +} + +nsresult nsMsgFilterList::RemoveFilterAt(uint32_t filterIndex) { + m_filters.RemoveElementAt(filterIndex); + return NS_OK; +} + +nsresult nsMsgFilterList::RemoveFilter(nsIMsgFilter* aFilter) { + m_filters.RemoveElement(aFilter); + return NS_OK; +} + +nsresult nsMsgFilterList::InsertFilterAt(uint32_t filterIndex, + nsIMsgFilter* aFilter) { + if (!m_temporaryList) aFilter->SetFilterList(this); + m_filters.InsertElementAt(filterIndex, aFilter); + + return NS_OK; +} + +// Attempt to move the filter at index filterIndex in the specified direction. +// If motion not possible in that direction, we still return success. +// We could return an error if the FE's want to beep or something. +nsresult nsMsgFilterList::MoveFilterAt(uint32_t filterIndex, + nsMsgFilterMotionValue motion) { + NS_ENSURE_ARG((motion == nsMsgFilterMotion::up) || + (motion == nsMsgFilterMotion::down)); + + uint32_t filterCount = 0; + nsresult rv = GetFilterCount(&filterCount); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_ARG(filterIndex < filterCount); + + uint32_t newIndex = filterIndex; + + if (motion == nsMsgFilterMotion::up) { + // are we already at the top? + if (filterIndex == 0) return NS_OK; + + newIndex = filterIndex - 1; + } else if (motion == nsMsgFilterMotion::down) { + // are we already at the bottom? + if (filterIndex == filterCount - 1) return NS_OK; + + newIndex = filterIndex + 1; + } + + nsCOMPtr<nsIMsgFilter> tempFilter1; + rv = GetFilterAt(newIndex, getter_AddRefs(tempFilter1)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFilter> tempFilter2; + rv = GetFilterAt(filterIndex, getter_AddRefs(tempFilter2)); + NS_ENSURE_SUCCESS(rv, rv); + + SetFilterAt(newIndex, tempFilter2); + SetFilterAt(filterIndex, tempFilter1); + + return NS_OK; +} + +nsresult nsMsgFilterList::MoveFilter(nsIMsgFilter* aFilter, + nsMsgFilterMotionValue motion) { + size_t filterIndex = m_filters.IndexOf(aFilter, 0); + NS_ENSURE_ARG(filterIndex != m_filters.NoIndex); + + return MoveFilterAt(filterIndex, motion); +} + +nsresult nsMsgFilterList::GetVersion(int16_t* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = m_fileVersion; + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilterList::MatchOrChangeFilterTarget( + const nsACString& oldFolderUri, const nsACString& newFolderUri, + bool caseInsensitive, bool* found) { + NS_ENSURE_ARG_POINTER(found); + + uint32_t numFilters = 0; + nsresult rv = GetFilterCount(&numFilters); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFilter> filter; + nsCString folderUri; + *found = false; + for (uint32_t index = 0; index < numFilters; index++) { + rv = GetFilterAt(index, getter_AddRefs(filter)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t numActions; + rv = filter->GetActionCount(&numActions); + NS_ENSURE_SUCCESS(rv, rv); + + for (uint32_t actionIndex = 0; actionIndex < numActions; actionIndex++) { + nsCOMPtr<nsIMsgRuleAction> filterAction; + rv = filter->GetActionAt(actionIndex, getter_AddRefs(filterAction)); + if (NS_FAILED(rv) || !filterAction) continue; + + nsMsgRuleActionType actionType; + if (NS_FAILED(filterAction->GetType(&actionType))) continue; + + if (actionType == nsMsgFilterAction::MoveToFolder || + actionType == nsMsgFilterAction::CopyToFolder) { + rv = filterAction->GetTargetFolderUri(folderUri); + if (NS_SUCCEEDED(rv) && !folderUri.IsEmpty()) { + bool matchFound = false; + if (caseInsensitive) { + if (folderUri.Equals(oldFolderUri, + nsCaseInsensitiveCStringComparator)) // local + matchFound = true; + } else { + if (folderUri.Equals(oldFolderUri)) // imap + matchFound = true; + } + if (matchFound) { + *found = true; + // if we just want to match the uri's, newFolderUri will be null + if (!newFolderUri.IsEmpty()) { + rv = filterAction->SetTargetFolderUri(newFolderUri); + NS_ENSURE_SUCCESS(rv, rv); + } + } + } + } + } + } + return rv; +} + +// this would only return true if any filter was on "any header", which we +// don't support in 6.x +NS_IMETHODIMP nsMsgFilterList::GetShouldDownloadAllHeaders(bool* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + + *aResult = false; + return NS_OK; +} + +// leaves m_arbitraryHeaders filed in with the arbitrary headers. +nsresult nsMsgFilterList::ComputeArbitraryHeaders() { + NS_ENSURE_TRUE(m_arbitraryHeaders.IsEmpty(), NS_OK); + + uint32_t numFilters = 0; + nsresult rv = GetFilterCount(&numFilters); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFilter> filter; + nsMsgSearchAttribValue attrib; + nsCString arbitraryHeader; + for (uint32_t index = 0; index < numFilters; index++) { + rv = GetFilterAt(index, getter_AddRefs(filter)); + if (!(NS_SUCCEEDED(rv) && filter)) continue; + + nsTArray<RefPtr<nsIMsgSearchTerm>> searchTerms; + filter->GetSearchTerms(searchTerms); + for (uint32_t i = 0; i < searchTerms.Length(); i++) { + filter->GetTerm(i, &attrib, nullptr, nullptr, nullptr, arbitraryHeader); + if (!arbitraryHeader.IsEmpty()) { + if (m_arbitraryHeaders.IsEmpty()) + m_arbitraryHeaders.Assign(arbitraryHeader); + else if (!FindInReadable(arbitraryHeader, m_arbitraryHeaders, + nsCaseInsensitiveCStringComparator)) { + m_arbitraryHeaders.Append(' '); + m_arbitraryHeaders.Append(arbitraryHeader); + } + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilterList::GetArbitraryHeaders(nsACString& aResult) { + ComputeArbitraryHeaders(); + aResult = m_arbitraryHeaders; + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilterList::FlushLogIfNecessary() { + // only flush the log if we are logging + if (m_loggingEnabled && m_logStream) { + nsresult rv = m_logStream->Flush(); + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; +} + +#define LOG_ENTRY_START_TAG "<p>\n" +#define LOG_ENTRY_START_TAG_LEN (strlen(LOG_ENTRY_START_TAG)) +#define LOG_ENTRY_END_TAG "</p>\n" +#define LOG_ENTRY_END_TAG_LEN (strlen(LOG_ENTRY_END_TAG)) + +NS_IMETHODIMP nsMsgFilterList::LogFilterMessage(const nsAString& message, + nsIMsgFilter* filter) { + if (!m_loggingEnabled) { + return NS_OK; + } + nsCOMPtr<nsIOutputStream> logStream; + GetLogStream(getter_AddRefs(logStream)); + if (!logStream) { + // Logging is on, but we failed to access the filter logfile. + // For completeness, we'll return an error, but we don't expect anyone + // to ever check it - logging failures shouldn't stop anything else. + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::components::StringBundle::Service(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIStringBundle> bundle; + nsresult rv = bundleService->CreateBundle( + "chrome://messenger/locale/filter.properties", getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + nsString tempMessage(message); + + if (filter) { + // If a filter was passed, prepend its name in the log message. + nsString filterName; + filter->GetFilterName(filterName); + + AutoTArray<nsString, 2> logFormatStrings = {filterName, tempMessage}; + nsString statusLogMessage; + rv = bundle->FormatStringFromName("filterMessage", logFormatStrings, + statusLogMessage); + if (NS_SUCCEEDED(rv)) tempMessage.Assign(statusLogMessage); + } + + // Prepare timestamp + PRExplodedTime exploded; + nsString dateValue; + PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &exploded); + mozilla::intl::DateTimeFormat::StyleBag style; + style.date = mozilla::Some(mozilla::intl::DateTimeFormat::Style::Short); + style.time = mozilla::Some(mozilla::intl::DateTimeFormat::Style::Long); + mozilla::intl::AppDateTimeFormat::Format(style, &exploded, dateValue); + + // HTML-escape the log for security reasons. + // We don't want someone to send us a message with a subject with + // HTML tags, especially <script>. + nsCString escapedBuffer; + nsAppendEscapedHTML(NS_ConvertUTF16toUTF8(tempMessage), escapedBuffer); + + // Print timestamp and the message. + AutoTArray<nsString, 2> logFormatStrings = {dateValue}; + CopyUTF8toUTF16(escapedBuffer, *logFormatStrings.AppendElement()); + nsString filterLogMessage; + rv = bundle->FormatStringFromName("filterLogLine", logFormatStrings, + filterLogMessage); + NS_ENSURE_SUCCESS(rv, rv); + + // Write message into log stream. + uint32_t writeCount; + rv = logStream->Write(LOG_ENTRY_START_TAG, LOG_ENTRY_START_TAG_LEN, + &writeCount); + NS_ENSURE_SUCCESS(rv, rv); + NS_ASSERTION(writeCount == LOG_ENTRY_START_TAG_LEN, + "failed to write out start log tag"); + + NS_ConvertUTF16toUTF8 buffer(filterLogMessage); + uint32_t escapedBufferLen = buffer.Length(); + rv = logStream->Write(buffer.get(), escapedBufferLen, &writeCount); + NS_ENSURE_SUCCESS(rv, rv); + NS_ASSERTION(writeCount == escapedBufferLen, "failed to write out log hit"); + + rv = logStream->Write(LOG_ENTRY_END_TAG, LOG_ENTRY_END_TAG_LEN, &writeCount); + NS_ENSURE_SUCCESS(rv, rv); + NS_ASSERTION(writeCount == LOG_ENTRY_END_TAG_LEN, + "failed to write out end log tag"); + return NS_OK; +} +// ------------ End FilterList methods ------------------ diff --git a/comm/mailnews/search/src/nsMsgFilterList.h b/comm/mailnews/search/src/nsMsgFilterList.h new file mode 100644 index 0000000000..536156854e --- /dev/null +++ b/comm/mailnews/search/src/nsMsgFilterList.h @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef _nsMsgFilterList_H_ +#define _nsMsgFilterList_H_ + +#include "nscore.h" +#include "nsIMsgFolder.h" +#include "nsIMsgFilterList.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" +#include "nsIFile.h" +#include "nsIOutputStream.h" + +const int16_t kFileVersion = 9; +const int16_t kManualContextVersion = 9; +const int16_t k60Beta1Version = 7; +const int16_t k45Version = 6; + +//////////////////////////////////////////////////////////////////////////////////////// +// The Msg Filter List is an interface designed to make accessing filter lists +// easier. Clients typically open a filter list and either enumerate the +// filters, or add new filters, or change the order around... +// +//////////////////////////////////////////////////////////////////////////////////////// + +class nsIMsgFilter; +class nsMsgFilter; + +class nsMsgFilterList : public nsIMsgFilterList { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGFILTERLIST + + nsMsgFilterList(); + + nsresult Close(); + nsresult LoadTextFilters(already_AddRefed<nsIInputStream> aStream); + + bool m_temporaryList; + + protected: + virtual ~nsMsgFilterList(); + + nsresult ComputeArbitraryHeaders(); + nsresult SaveTextFilters(nsIOutputStream* aStream); + // file streaming methods + int ReadChar(nsIInputStream* aStream); + int SkipWhitespace(nsIInputStream* aStream); + bool StrToBool(nsCString& str); + int LoadAttrib(nsMsgFilterFileAttribValue& attrib, nsIInputStream* aStream); + const char* GetStringForAttrib(nsMsgFilterFileAttribValue attrib); + nsresult LoadValue(nsCString& value, nsIInputStream* aStream); + int16_t m_fileVersion; + bool m_loggingEnabled; + bool m_startWritingToBuffer; // tells us when to start writing one whole + // filter to m_unparsedBuffer + nsCOMPtr<nsIMsgFolder> m_folder; + nsMsgFilter* m_curFilter; // filter we're filing in or out(?) + nsCString m_listId; + nsTArray<nsCOMPtr<nsIMsgFilter> > m_filters; + nsCString m_arbitraryHeaders; + nsCOMPtr<nsIFile> m_defaultFile; + nsCString m_unparsedFilterBuffer; // holds one entire filter unparsed + + private: + nsresult GetLogFile(nsIFile** aFile); + nsresult EnsureLogFile(nsIFile* file); + nsCOMPtr<nsIOutputStream> m_logStream; +}; + +#endif diff --git a/comm/mailnews/search/src/nsMsgFilterService.cpp b/comm/mailnews/search/src/nsMsgFilterService.cpp new file mode 100644 index 0000000000..1adfe7cee9 --- /dev/null +++ b/comm/mailnews/search/src/nsMsgFilterService.cpp @@ -0,0 +1,1374 @@ +/* -*- 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/. */ + +// this file implements the nsMsgFilterService interface + +#include "msgCore.h" +#include "nsMsgFilterService.h" +#include "nsMsgFilterList.h" +#include "nsMsgSearchScopeTerm.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIPrompt.h" +#include "nsIDocShell.h" +#include "nsIStringBundle.h" +#include "nsIMsgSearchNotify.h" +#include "nsIUrlListener.h" +#include "nsIMsgCopyServiceListener.h" +#include "nsIMsgLocalMailFolder.h" +#include "nsIMsgDatabase.h" +#include "nsIMsgHdr.h" +#include "nsIMsgCopyService.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsISafeOutputStream.h" +#include "nsIMsgComposeService.h" +#include "nsNetUtil.h" +#include "nsMsgUtils.h" +#include "nsIMsgMailSession.h" +#include "nsIFile.h" +#include "nsIMsgFilterCustomAction.h" +#include "nsMsgMessageFlags.h" +#include "nsIMsgWindow.h" +#include "nsIMsgSearchCustomTerm.h" +#include "nsIMsgSearchTerm.h" +#include "nsIMsgThread.h" +#include "nsIMsgFilter.h" +#include "nsIMsgOperationListener.h" +#include "mozilla/Components.h" +#include "mozilla/Logging.h" + +using namespace mozilla; + +LazyLogModule FILTERLOGMODULE("Filters"); + +#define BREAK_IF_FAILURE(_rv, _text) \ + if (NS_FAILED(_rv)) { \ + MOZ_LOG(FILTERLOGMODULE, LogLevel::Error, \ + ("(Post) Filter error: %s", _text)); \ + m_filters->LogFilterMessage(NS_LITERAL_STRING_FROM_CSTRING(_text), \ + m_curFilter); \ + NS_WARNING(_text); \ + mFinalResult = _rv; \ + break; \ + } + +#define CONTINUE_IF_FAILURE(_rv, _text) \ + if (NS_FAILED(_rv)) { \ + MOZ_LOG(FILTERLOGMODULE, LogLevel::Warning, \ + ("(Post) Filter problem: %s", _text)); \ + m_filters->LogFilterMessage(NS_LITERAL_STRING_FROM_CSTRING(_text), \ + m_curFilter); \ + NS_WARNING(_text); \ + mFinalResult = _rv; \ + if (m_msgWindow && !ContinueExecutionPrompt()) return OnEndExecution(); \ + continue; \ + } + +#define BREAK_IF_FALSE(_assertTrue, _text) \ + if (MOZ_UNLIKELY(!(_assertTrue))) { \ + MOZ_LOG(FILTERLOGMODULE, LogLevel::Error, \ + ("(Post) Filter error: %s", _text)); \ + m_filters->LogFilterMessage(NS_LITERAL_STRING_FROM_CSTRING(_text), \ + m_curFilter); \ + NS_WARNING(_text); \ + mFinalResult = NS_ERROR_FAILURE; \ + break; \ + } + +#define CONTINUE_IF_FALSE(_assertTrue, _text) \ + if (MOZ_UNLIKELY(!(_assertTrue))) { \ + MOZ_LOG(FILTERLOGMODULE, LogLevel::Warning, \ + ("(Post) Filter problem: %s", _text)); \ + m_filters->LogFilterMessage(NS_LITERAL_STRING_FROM_CSTRING(_text), \ + m_curFilter); \ + NS_WARNING(_text); \ + mFinalResult = NS_ERROR_FAILURE; \ + if (m_msgWindow && !ContinueExecutionPrompt()) return OnEndExecution(); \ + continue; \ + } + +#define BREAK_ACTION(_text) \ + MOZ_LOG(FILTERLOGMODULE, LogLevel::Error, \ + ("(Post) Filter Error: %s", _text)); \ + if (loggingEnabled) \ + m_filters->LogFilterMessage(NS_LITERAL_STRING_FROM_CSTRING(_text), \ + m_curFilter); \ + NS_WARNING(_text); \ + if (m_msgWindow && !ContinueExecutionPrompt()) return OnEndExecution(); \ + break; + +#define BREAK_ACTION_IF_FALSE(_assertTrue, _text) \ + if (MOZ_UNLIKELY(!(_assertTrue))) { \ + finalResult = NS_ERROR_FAILURE; \ + BREAK_ACTION(_text); \ + } + +#define BREAK_ACTION_IF_FAILURE(_rv, _text) \ + if (NS_FAILED(_rv)) { \ + finalResult = _rv; \ + BREAK_ACTION(_text); \ + } + +NS_IMPL_ISUPPORTS(nsMsgFilterService, nsIMsgFilterService) + +nsMsgFilterService::nsMsgFilterService() { + MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug, ("nsMsgFilterService")); +} + +nsMsgFilterService::~nsMsgFilterService() { + MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug, ("~nsMsgFilterService")); +} + +NS_IMETHODIMP nsMsgFilterService::OpenFilterList( + nsIFile* aFilterFile, nsIMsgFolder* rootFolder, nsIMsgWindow* aMsgWindow, + nsIMsgFilterList** resultFilterList) { + NS_ENSURE_ARG_POINTER(aFilterFile); + NS_ENSURE_ARG_POINTER(resultFilterList); + + nsresult rv; + if (rootFolder) { + nsCOMPtr<nsIMsgIncomingServer> server; + rv = rootFolder->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + nsString serverName; + server->GetPrettyName(serverName); + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("Reading filter list for account '%s'", + NS_ConvertUTF16toUTF8(serverName).get())); + } + + nsString fileName; + (void)aFilterFile->GetPath(fileName); + MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug, + ("Reading filter list from file '%s'", + NS_ConvertUTF16toUTF8(fileName).get())); + + bool exists = false; + rv = aFilterFile->Exists(&exists); + if (NS_FAILED(rv) || !exists) { + rv = aFilterFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr<nsIInputStream> fileStream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), aFilterFile); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(fileStream, NS_ERROR_OUT_OF_MEMORY); + + RefPtr<nsMsgFilterList> filterList = new nsMsgFilterList(); + filterList->SetFolder(rootFolder); + + // temporarily tell the filter where its file path is + filterList->SetDefaultFile(aFilterFile); + + int64_t size = 0; + rv = aFilterFile->GetFileSize(&size); + if (NS_SUCCEEDED(rv) && size > 0) + rv = filterList->LoadTextFilters(fileStream.forget()); + if (NS_SUCCEEDED(rv)) { + int16_t version; + filterList->GetVersion(&version); + if (version != kFileVersion) SaveFilterList(filterList, aFilterFile); + } else { + if (rv == NS_MSG_FILTER_PARSE_ERROR && aMsgWindow) { + rv = BackUpFilterFile(aFilterFile, aMsgWindow); + NS_ENSURE_SUCCESS(rv, rv); + rv = aFilterFile->SetFileSize(0); + NS_ENSURE_SUCCESS(rv, rv); + return OpenFilterList(aFilterFile, rootFolder, aMsgWindow, + resultFilterList); + } else if (rv == NS_MSG_CUSTOM_HEADERS_OVERFLOW && aMsgWindow) + ThrowAlertMsg("filterCustomHeaderOverflow", aMsgWindow); + else if (rv == NS_MSG_INVALID_CUSTOM_HEADER && aMsgWindow) + ThrowAlertMsg("invalidCustomHeader", aMsgWindow); + } + + nsCString listId; + filterList->GetListId(listId); + uint32_t filterCount = 0; + (void)filterList->GetFilterCount(&filterCount); + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("Read %" PRIu32 " filters", filterCount)); + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("Filter list stored as %s", listId.get())); + + filterList.forget(resultFilterList); + return rv; +} + +NS_IMETHODIMP nsMsgFilterService::CloseFilterList( + nsIMsgFilterList* filterList) { + // NS_ASSERTION(false,"CloseFilterList doesn't do anything yet"); + return NS_OK; +} + +/* save without deleting */ +NS_IMETHODIMP nsMsgFilterService::SaveFilterList(nsIMsgFilterList* filterList, + nsIFile* filterFile) { + NS_ENSURE_ARG_POINTER(filterFile); + NS_ENSURE_ARG_POINTER(filterList); + + nsCString listId; + filterList->GetListId(listId); + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("Saving filter list %s", listId.get())); + + nsCOMPtr<nsIOutputStream> strm; + nsresult rv = MsgNewSafeBufferedFileOutputStream(getter_AddRefs(strm), + filterFile, -1, 0600); + NS_ENSURE_SUCCESS(rv, rv); + + rv = filterList->SaveToFile(strm); + + nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(strm); + NS_ASSERTION(safeStream, "expected a safe output stream!"); + if (safeStream) { + rv = safeStream->Finish(); + if (NS_FAILED(rv)) { + NS_WARNING("failed to save filter file! possible data loss"); + MOZ_LOG(FILTERLOGMODULE, LogLevel::Error, ("Save of list failed")); + } + } + return rv; +} + +NS_IMETHODIMP nsMsgFilterService::CancelFilterList( + nsIMsgFilterList* filterList) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult nsMsgFilterService::BackUpFilterFile(nsIFile* aFilterFile, + nsIMsgWindow* aMsgWindow) { + AlertBackingUpFilterFile(aMsgWindow); + + nsCOMPtr<nsIFile> localParentDir; + nsresult rv = aFilterFile->GetParent(getter_AddRefs(localParentDir)); + NS_ENSURE_SUCCESS(rv, rv); + + // if back-up file exists delete the back up file otherwise copy fails. + nsCOMPtr<nsIFile> backupFile; + rv = localParentDir->Clone(getter_AddRefs(backupFile)); + NS_ENSURE_SUCCESS(rv, rv); + backupFile->AppendNative("rulesbackup.dat"_ns); + bool exists; + backupFile->Exists(&exists); + if (exists) backupFile->Remove(false); + + return aFilterFile->CopyToNative(localParentDir, "rulesbackup.dat"_ns); +} + +nsresult nsMsgFilterService::AlertBackingUpFilterFile( + nsIMsgWindow* aMsgWindow) { + return ThrowAlertMsg("filterListBackUpMsg", aMsgWindow); +} + +// Do not use this routine if you have to call it very often because it creates +// a new bundle each time. +nsresult nsMsgFilterService::GetStringFromBundle(const char* aMsgName, + nsAString& aResult) { + nsCOMPtr<nsIStringBundle> bundle; + nsresult rv = GetFilterStringBundle(getter_AddRefs(bundle)); + if (NS_SUCCEEDED(rv) && bundle) + rv = bundle->GetStringFromName(aMsgName, aResult); + return rv; +} + +nsresult nsMsgFilterService::GetFilterStringBundle(nsIStringBundle** aBundle) { + NS_ENSURE_ARG_POINTER(aBundle); + + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::components::StringBundle::Service(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + nsCOMPtr<nsIStringBundle> bundle; + if (bundleService) + bundleService->CreateBundle("chrome://messenger/locale/filter.properties", + getter_AddRefs(bundle)); + bundle.forget(aBundle); + return NS_OK; +} + +nsresult nsMsgFilterService::ThrowAlertMsg(const char* aMsgName, + nsIMsgWindow* aMsgWindow) { + nsString alertString; + nsresult rv = GetStringFromBundle(aMsgName, alertString); + nsCOMPtr<nsIMsgWindow> msgWindow = aMsgWindow; + if (!msgWindow) { + nsCOMPtr<nsIMsgMailSession> mailSession( + do_GetService("@mozilla.org/messenger/services/session;1", &rv)); + if (NS_SUCCEEDED(rv)) + rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow)); + } + + if (NS_SUCCEEDED(rv) && !alertString.IsEmpty() && msgWindow) { + nsCOMPtr<nsIDocShell> docShell; + msgWindow->GetRootDocShell(getter_AddRefs(docShell)); + if (docShell) { + nsCOMPtr<nsIPrompt> dialog(do_GetInterface(docShell)); + if (dialog && !alertString.IsEmpty()) + dialog->Alert(nullptr, alertString.get()); + } + } + return rv; +} + +// this class is used to run filters after the fact, i.e., after new mail has +// been downloaded from the server. It can do the following: +// 1. Apply a single imap or pop3 filter on a single folder. +// 2. Apply multiple filters on a single imap or pop3 folder. +// 3. Apply a single filter on multiple imap or pop3 folders in the same +// account. +// 4. Apply multiple filters on multiple imap or pop3 folders in the same +// account. +// This will be called from the front end js code in the case of the +// apply filters to folder menu code, and from the filter dialog js code with +// the run filter now command. + +// this class holds the list of filters and folders, and applies them in turn, +// first iterating over all the filters on one folder, and then advancing to the +// next folder and repeating. For each filter,we take the filter criteria and +// create a search term list. Then, we execute the search. We are a search +// listener so that we can build up the list of search hits. Then, when the +// search is done, we will apply the filter action(s) en-masse, so, for example, +// if the action is a move, we calls one method to move all the messages to the +// destination folder. Or, mark all the messages read. In the case of imap +// operations, or imap/local moves, the action will be asynchronous, so we'll +// need to be a url listener as well, and kick off the next filter when the +// action completes. +class nsMsgFilterAfterTheFact : public nsIUrlListener, + public nsIMsgSearchNotify, + public nsIMsgCopyServiceListener { + public: + nsMsgFilterAfterTheFact(nsIMsgWindow* aMsgWindow, + nsIMsgFilterList* aFilterList, + const nsTArray<RefPtr<nsIMsgFolder>>& aFolderList, + nsIMsgOperationListener* aCallback); + NS_DECL_ISUPPORTS + NS_DECL_NSIURLLISTENER + NS_DECL_NSIMSGSEARCHNOTIFY + NS_DECL_NSIMSGCOPYSERVICELISTENER + + nsresult AdvanceToNextFolder(); // kicks off the process + protected: + virtual ~nsMsgFilterAfterTheFact(); + virtual nsresult RunNextFilter(); + /** + * apply filter actions to current search hits + */ + nsresult ApplyFilter(); + nsresult OnEndExecution(); // do what we have to do to cleanup. + bool ContinueExecutionPrompt(); + nsresult DisplayConfirmationPrompt(nsIMsgWindow* msgWindow, + const char16_t* confirmString, + bool* confirmed); + nsCOMPtr<nsIMsgWindow> m_msgWindow; + nsCOMPtr<nsIMsgFilterList> m_filters; + nsTArray<RefPtr<nsIMsgFolder>> m_folders; + nsCOMPtr<nsIMsgFolder> m_curFolder; + nsCOMPtr<nsIMsgDatabase> m_curFolderDB; + nsCOMPtr<nsIMsgFilter> m_curFilter; + uint32_t m_curFilterIndex; + uint32_t m_curFolderIndex; + uint32_t m_numFilters; + nsTArray<nsMsgKey> m_searchHits; + nsTArray<RefPtr<nsIMsgDBHdr>> m_searchHitHdrs; + nsTArray<nsMsgKey> m_stopFiltering; + nsCOMPtr<nsIMsgSearchSession> m_searchSession; + nsCOMPtr<nsIMsgOperationListener> m_callback; + uint32_t m_nextAction; // next filter action to perform + nsresult mFinalResult; // report of overall success or failure + bool mNeedsRelease; // Did we need to release ourself? +}; + +NS_IMPL_ISUPPORTS(nsMsgFilterAfterTheFact, nsIUrlListener, nsIMsgSearchNotify, + nsIMsgCopyServiceListener) + +nsMsgFilterAfterTheFact::nsMsgFilterAfterTheFact( + nsIMsgWindow* aMsgWindow, nsIMsgFilterList* aFilterList, + const nsTArray<RefPtr<nsIMsgFolder>>& aFolderList, + nsIMsgOperationListener* aCallback) { + MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug, ("(Post) nsMsgFilterAfterTheFact")); + m_curFilterIndex = m_curFolderIndex = m_nextAction = 0; + m_msgWindow = aMsgWindow; + m_filters = aFilterList; + m_folders = aFolderList.Clone(); + m_filters->GetFilterCount(&m_numFilters); + + NS_ADDREF_THIS(); // we own ourselves, and will release ourselves when + // execution is done. + mNeedsRelease = true; + + m_callback = aCallback; + mFinalResult = NS_OK; +} + +nsMsgFilterAfterTheFact::~nsMsgFilterAfterTheFact() { + MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug, + ("(Post) ~nsMsgFilterAfterTheFact")); +} + +// do what we have to do to cleanup. +nsresult nsMsgFilterAfterTheFact::OnEndExecution() { + if (m_searchSession) m_searchSession->UnregisterListener(this); + + if (m_filters) (void)m_filters->FlushLogIfNecessary(); + + if (m_callback) (void)m_callback->OnStopOperation(mFinalResult); + + nsresult rv = mFinalResult; + // OnEndExecution() can be called a second time when a rule execution fails + // and the user is prompted whether he wants to continue. + if (mNeedsRelease) { + NS_RELEASE_THIS(); // release ourselves. + mNeedsRelease = false; + } + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, ("(Post) End executing filters")); + return rv; +} + +nsresult nsMsgFilterAfterTheFact::RunNextFilter() { + MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug, + ("(Post) nsMsgFilterAfterTheFact::RunNextFilter")); + nsresult rv = NS_OK; + while (true) { + m_curFilter = nullptr; + if (m_curFilterIndex >= m_numFilters) break; + + BREAK_IF_FALSE(m_filters, "Missing filters"); + + MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug, + ("(Post) Running filter %" PRIu32, m_curFilterIndex)); + + rv = + m_filters->GetFilterAt(m_curFilterIndex++, getter_AddRefs(m_curFilter)); + CONTINUE_IF_FAILURE(rv, "Could not get filter at index"); + + nsString filterName; + m_curFilter->GetFilterName(filterName); + // clang-format off + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("(Post) Filter name: %s", NS_ConvertUTF16toUTF8(filterName).get())); + // clang-format on + + nsTArray<RefPtr<nsIMsgSearchTerm>> searchTerms; + rv = m_curFilter->GetSearchTerms(searchTerms); + CONTINUE_IF_FAILURE(rv, "Could not get searchTerms"); + + if (m_searchSession) m_searchSession->UnregisterListener(this); + m_searchSession = + do_CreateInstance("@mozilla.org/messenger/searchSession;1", &rv); + BREAK_IF_FAILURE(rv, "Failed to get search session"); + + nsMsgSearchScopeValue searchScope = nsMsgSearchScope::offlineMail; + for (nsIMsgSearchTerm* term : searchTerms) { + rv = m_searchSession->AppendTerm(term); + BREAK_IF_FAILURE(rv, "Could not append search term"); + } + CONTINUE_IF_FAILURE(rv, "Failed to setup search terms"); + m_searchSession->RegisterListener(this, + nsIMsgSearchSession::allNotifications); + + rv = m_searchSession->AddScopeTerm(searchScope, m_curFolder); + CONTINUE_IF_FAILURE(rv, "Failed to add scope term"); + m_nextAction = 0; + rv = m_searchSession->Search(m_msgWindow); + CONTINUE_IF_FAILURE(rv, "Search failed"); + return NS_OK; // OnSearchDone will continue + } + + if (NS_FAILED(rv)) { + MOZ_LOG(FILTERLOGMODULE, LogLevel::Error, + ("(Post) Filter evaluation failed")); + m_filters->LogFilterMessage(u"Filter evaluation failed"_ns, m_curFilter); + } + + m_curFilter = nullptr; + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Search failed"); + return AdvanceToNextFolder(); +} + +nsresult nsMsgFilterAfterTheFact::AdvanceToNextFolder() { + MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug, + ("(Post) nsMsgFilterAfterTheFact::AdvanceToNextFolder")); + nsresult rv = NS_OK; + // Advance through folders, making sure m_curFolder is null on errors + while (true) { + m_stopFiltering.Clear(); + m_curFolder = nullptr; + if (m_curFolderIndex >= m_folders.Length()) { + // final end of nsMsgFilterAfterTheFact object + return OnEndExecution(); + } + + MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug, + ("(Post) Entering folder %" PRIu32, m_curFolderIndex)); + + // reset the filter index to apply all filters to this new folder + m_curFilterIndex = 0; + m_nextAction = 0; + m_curFolder = m_folders[m_curFolderIndex++]; + + // Note: I got rv = NS_OK but null m_curFolder after deleting a folder + // outside of TB, when I select a single message and "run filter on message" + // and the filter is to move the message to the deleted folder. + + // m_curFolder may be null when the folder is deleted externally. + CONTINUE_IF_FALSE(m_curFolder, "Next folder returned null"); + + nsString folderName; + (void)m_curFolder->GetName(folderName); + MOZ_LOG( + FILTERLOGMODULE, LogLevel::Info, + ("(Post) Folder name: %s", NS_ConvertUTF16toUTF8(folderName).get())); + + nsCOMPtr<nsIFile> folderPath; + (void)m_curFolder->GetFilePath(getter_AddRefs(folderPath)); + (void)folderPath->GetPath(folderName); + MOZ_LOG( + FILTERLOGMODULE, LogLevel::Debug, + ("(Post) Folder path: %s", NS_ConvertUTF16toUTF8(folderName).get())); + + rv = m_curFolder->GetMsgDatabase(getter_AddRefs(m_curFolderDB)); + if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) { + nsCOMPtr<nsIMsgLocalMailFolder> localFolder = + do_QueryInterface(m_curFolder, &rv); + if (NS_SUCCEEDED(rv) && localFolder) + // will continue with OnStopRunningUrl + return localFolder->ParseFolder(m_msgWindow, this); + } + CONTINUE_IF_FAILURE(rv, "Could not get folder db"); + + rv = RunNextFilter(); + // RunNextFilter returns success when either filters are done, or an async + // process has started. It will call AdvanceToNextFolder itself if possible, + // so no need to call here. + BREAK_IF_FAILURE(rv, "Failed to run next filter"); + break; + } + return rv; +} + +NS_IMETHODIMP nsMsgFilterAfterTheFact::OnStartRunningUrl(nsIURI* aUrl) { + return NS_OK; +} + +// This is the return from a folder parse +NS_IMETHODIMP nsMsgFilterAfterTheFact::OnStopRunningUrl(nsIURI* aUrl, + nsresult aExitCode) { + if (NS_SUCCEEDED(aExitCode)) return RunNextFilter(); + + mFinalResult = aExitCode; + // If m_msgWindow then we are in a context where the user can deal with + // errors. Put up a prompt, and exit if user wants. + if (m_msgWindow && !ContinueExecutionPrompt()) return OnEndExecution(); + + // folder parse failed, so stop processing this folder. + return AdvanceToNextFolder(); +} + +NS_IMETHODIMP nsMsgFilterAfterTheFact::OnSearchHit(nsIMsgDBHdr* header, + nsIMsgFolder* folder) { + NS_ENSURE_ARG_POINTER(header); + + nsMsgKey msgKey; + header->GetMessageKey(&msgKey); + + nsCString msgId; + header->GetMessageId(getter_Copies(msgId)); + // clang-format off + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("(Post) Filter matched message with key %" PRIu32, + msgKeyToInt(msgKey))); + // clang-format on + MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug, + ("(Post) Matched message ID: %s", msgId.get())); + + // Under various previous actions (a move, delete, or stopExecution) + // we do not want to process filters on a per-message basis. + if (m_stopFiltering.Contains(msgKey)) { + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("(Post) Stopping further filter execution on this message")); + return NS_OK; + } + + m_searchHits.AppendElement(msgKey); + m_searchHitHdrs.AppendElement(header); + return NS_OK; +} + +// Continue after an async operation. +NS_IMETHODIMP nsMsgFilterAfterTheFact::OnSearchDone(nsresult status) { + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("(Post) Done matching current filter")); + if (NS_SUCCEEDED(status)) + return m_searchHits.IsEmpty() ? RunNextFilter() : ApplyFilter(); + + mFinalResult = status; + if (m_msgWindow && !ContinueExecutionPrompt()) return OnEndExecution(); + + // The search failed, so move on to the next filter. + return RunNextFilter(); +} + +NS_IMETHODIMP nsMsgFilterAfterTheFact::OnNewSearch() { + m_searchHits.Clear(); + m_searchHitHdrs.Clear(); + return NS_OK; +} + +// This method will apply filters. It will continue to advance though headers, +// filters, and folders until done, unless it starts an async operation with +// a callback. The callback should call ApplyFilter again. It only returns +// an error if it is impossible to continue after attempting to continue the +// next filter action, filter, or folder. +nsresult nsMsgFilterAfterTheFact::ApplyFilter() { + MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug, + ("(Post) nsMsgFilterAfterTheFact::ApplyFilter")); + nsresult rv; + do { + // Error management block, break if unable to continue with filter. + + if (!m_curFilter) + break; // Maybe not an error, we just need to call RunNextFilter(); + if (!m_curFolder) + break; // Maybe not an error, we just need to call AdvanceToNextFolder(); + + // 'm_curFolder' can be reset asynchronously by the copy service + // calling OnStopCopy(). So take a local copy here and use it throughout the + // function. + nsCOMPtr<nsIMsgFolder> curFolder = m_curFolder; + nsCOMPtr<nsIMsgFilter> curFilter = m_curFilter; + + // We're going to log the filter actions before firing them because some + // actions are async. + bool loggingEnabled = false; + if (m_filters) (void)m_filters->GetLoggingEnabled(&loggingEnabled); + + nsTArray<RefPtr<nsIMsgRuleAction>> actionList; + rv = curFilter->GetSortedActionList(actionList); + BREAK_IF_FAILURE(rv, "Could not get action list for filter"); + + uint32_t numActions = actionList.Length(); + + if (m_nextAction == 0) { + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("(Post) Applying %" PRIu32 " filter actions to %" PRIu32 + " matched messages", + numActions, static_cast<uint32_t>(m_searchHits.Length()))); + } else if (m_nextAction < numActions) { + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("(Post) Applying remaining %" PRIu32 + " filter actions to %" PRIu32 " matched messages", + numActions - m_nextAction, + static_cast<uint32_t>(m_searchHits.Length()))); + } + + // We start from m_nextAction to allow us to continue applying actions + // after the return from an async copy. + while (m_nextAction < numActions) { + nsresult finalResult = NS_OK; + nsCOMPtr<nsIMsgRuleAction> filterAction(actionList[m_nextAction]); + ++m_nextAction; + + nsMsgRuleActionType actionType; + rv = filterAction->GetType(&actionType); + CONTINUE_IF_FAILURE(rv, "Could not get type for filter action"); + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("(Post) Running filter action at index %" PRIu32 + ", action type = %i", + m_nextAction - 1, actionType)); + + nsCString actionTargetFolderUri; + if (actionType == nsMsgFilterAction::MoveToFolder || + actionType == nsMsgFilterAction::CopyToFolder) { + rv = filterAction->GetTargetFolderUri(actionTargetFolderUri); + CONTINUE_IF_FAILURE(rv, "GetTargetFolderUri failed"); + CONTINUE_IF_FALSE(!actionTargetFolderUri.IsEmpty(), + "actionTargetFolderUri is empty"); + } + + if (loggingEnabled) { + for (auto msgHdr : m_searchHitHdrs) { + (void)curFilter->LogRuleHit(filterAction, msgHdr); + } + } + + // all actions that pass "this" as a listener in order to chain filter + // execution when the action is finished need to return before reaching + // the bottom of this routine, because we run the next filter at the end + // of this routine. + switch (actionType) { + case nsMsgFilterAction::Delete: + // we can't pass ourselves in as a copy service listener because the + // copy service listener won't get called in several situations (e.g., + // the delete model is imap delete) and we rely on the listener + // getting called to continue the filter application. This means we're + // going to end up firing off the delete, and then subsequently + // issuing a search for the next filter, which will block until the + // delete finishes. + rv = curFolder->DeleteMessages(m_searchHitHdrs, m_msgWindow, false, + false, nullptr, false /*allow Undo*/); + BREAK_ACTION_IF_FAILURE(rv, "Deleting messages failed"); + + // don't allow any more filters on this message + m_stopFiltering.AppendElements(m_searchHits); + for (uint32_t i = 0; i < m_searchHits.Length(); i++) + curFolder->OrProcessingFlags(m_searchHits[i], + nsMsgProcessingFlags::FilterToMove); + // if we are deleting then we couldn't care less about applying + // remaining filter actions + m_nextAction = numActions; + break; + + case nsMsgFilterAction::MoveToFolder: + // Even if move fails we will not run additional actions, as they + // would not have run if move succeeded. + m_nextAction = numActions; + // Fall through to the copy case. + [[fallthrough]]; + case nsMsgFilterAction::CopyToFolder: { + nsCString uri; + curFolder->GetURI(uri); + + if (uri.Equals(actionTargetFolderUri)) { + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("(Post) Target folder is the same as source folder, " + "skipping")); + break; + } + + nsCOMPtr<nsIMsgFolder> destIFolder; + rv = GetOrCreateFolder(actionTargetFolderUri, + getter_AddRefs(destIFolder)); + BREAK_ACTION_IF_FAILURE(rv, "Could not get action folder"); + + bool canFileMessages = true; + nsCOMPtr<nsIMsgFolder> parentFolder; + destIFolder->GetParent(getter_AddRefs(parentFolder)); + if (parentFolder) destIFolder->GetCanFileMessages(&canFileMessages); + if (!parentFolder || !canFileMessages) { + curFilter->SetEnabled(false); + destIFolder->ThrowAlertMsg("filterDisabled", m_msgWindow); + // we need to explicitly save the filter file. + m_filters->SaveToDefaultFile(); + // In the case of applying multiple filters + // we might want to remove the filter from the list, but + // that's a bit evil since we really don't know that we own + // the list. Disabling it doesn't do a lot of good since + // we still apply disabled filters. Currently, we don't + // have any clients that apply filters to multiple folders, + // so this might be the edge case of an edge case. + m_nextAction = numActions; + BREAK_ACTION_IF_FALSE(false, + "No parent folder or folder can't file " + "messages, disabling the filter"); + } + nsCOMPtr<nsIMsgCopyService> copyService = + do_GetService("@mozilla.org/messenger/messagecopyservice;1", &rv); + BREAK_ACTION_IF_FAILURE(rv, "Could not get copy service"); + + if (actionType == nsMsgFilterAction::MoveToFolder) { + m_stopFiltering.AppendElements(m_searchHits); + for (uint32_t i = 0; i < m_searchHits.Length(); i++) + curFolder->OrProcessingFlags(m_searchHits[i], + nsMsgProcessingFlags::FilterToMove); + } + + rv = copyService->CopyMessages( + curFolder, m_searchHitHdrs, destIFolder, + actionType == nsMsgFilterAction::MoveToFolder, this, m_msgWindow, + false); + BREAK_ACTION_IF_FAILURE(rv, "CopyMessages failed"); + MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug, + ("(Post) Action execution continues async")); + return NS_OK; // OnStopCopy callback to continue; + } break; + case nsMsgFilterAction::MarkRead: + // crud, no listener support here - we'll probably just need to go on + // and apply the next filter, and, in the imap case, rely on multiple + // connection and url queueing to stay out of trouble + rv = curFolder->MarkMessagesRead(m_searchHitHdrs, true); + BREAK_ACTION_IF_FAILURE(rv, "Setting message flags failed"); + break; + case nsMsgFilterAction::MarkUnread: + rv = curFolder->MarkMessagesRead(m_searchHitHdrs, false); + BREAK_ACTION_IF_FAILURE(rv, "Setting message flags failed"); + break; + case nsMsgFilterAction::MarkFlagged: + rv = curFolder->MarkMessagesFlagged(m_searchHitHdrs, true); + BREAK_ACTION_IF_FAILURE(rv, "Setting message flags failed"); + break; + case nsMsgFilterAction::KillThread: + case nsMsgFilterAction::WatchThread: { + for (auto msgHdr : m_searchHitHdrs) { + nsCOMPtr<nsIMsgThread> msgThread; + nsMsgKey threadKey; + m_curFolderDB->GetThreadContainingMsgHdr(msgHdr, + getter_AddRefs(msgThread)); + BREAK_ACTION_IF_FALSE(msgThread, "Could not find msg thread"); + msgThread->GetThreadKey(&threadKey); + if (actionType == nsMsgFilterAction::KillThread) { + rv = m_curFolderDB->MarkThreadIgnored(msgThread, threadKey, true, + nullptr); + BREAK_ACTION_IF_FAILURE(rv, "Setting message flags failed"); + } else { + rv = m_curFolderDB->MarkThreadWatched(msgThread, threadKey, true, + nullptr); + BREAK_ACTION_IF_FAILURE(rv, "Setting message flags failed"); + } + } + } break; + case nsMsgFilterAction::KillSubthread: { + for (auto msgHdr : m_searchHitHdrs) { + rv = m_curFolderDB->MarkHeaderKilled(msgHdr, true, nullptr); + BREAK_ACTION_IF_FAILURE(rv, "Setting message flags failed"); + } + } break; + case nsMsgFilterAction::ChangePriority: { + nsMsgPriorityValue filterPriority; + filterAction->GetPriority(&filterPriority); + for (auto msgHdr : m_searchHitHdrs) { + rv = msgHdr->SetPriority(filterPriority); + BREAK_ACTION_IF_FAILURE(rv, "Setting message flags failed"); + } + } break; + case nsMsgFilterAction::AddTag: { + nsCString keyword; + filterAction->GetStrValue(keyword); + rv = curFolder->AddKeywordsToMessages(m_searchHitHdrs, keyword); + BREAK_ACTION_IF_FAILURE(rv, "Setting message flags failed"); + } break; + case nsMsgFilterAction::JunkScore: { + nsAutoCString junkScoreStr; + int32_t junkScore; + filterAction->GetJunkScore(&junkScore); + junkScoreStr.AppendInt(junkScore); + rv = + curFolder->SetJunkScoreForMessages(m_searchHitHdrs, junkScoreStr); + BREAK_ACTION_IF_FAILURE(rv, "Setting message flags failed"); + } break; + case nsMsgFilterAction::Forward: { + nsCOMPtr<nsIMsgIncomingServer> server; + rv = curFolder->GetServer(getter_AddRefs(server)); + BREAK_ACTION_IF_FAILURE(rv, "Could not get server"); + nsCString forwardTo; + filterAction->GetStrValue(forwardTo); + BREAK_ACTION_IF_FALSE(!forwardTo.IsEmpty(), "blank forwardTo URI"); + nsCOMPtr<nsIMsgComposeService> compService = + do_GetService("@mozilla.org/messengercompose;1", &rv); + BREAK_ACTION_IF_FAILURE(rv, "Could not get compose service"); + + for (auto msgHdr : m_searchHitHdrs) { + rv = compService->ForwardMessage( + NS_ConvertASCIItoUTF16(forwardTo), msgHdr, m_msgWindow, server, + nsIMsgComposeService::kForwardAsDefault); + BREAK_ACTION_IF_FAILURE(rv, "Forward action failed"); + } + } break; + case nsMsgFilterAction::Reply: { + nsCString replyTemplateUri; + filterAction->GetStrValue(replyTemplateUri); + BREAK_ACTION_IF_FALSE(!replyTemplateUri.IsEmpty(), + "Empty reply template URI"); + + nsCOMPtr<nsIMsgIncomingServer> server; + rv = curFolder->GetServer(getter_AddRefs(server)); + BREAK_ACTION_IF_FAILURE(rv, "Could not get server"); + + nsCOMPtr<nsIMsgComposeService> compService = + do_GetService("@mozilla.org/messengercompose;1", &rv); + BREAK_ACTION_IF_FAILURE(rv, "Could not get compose service"); + for (auto msgHdr : m_searchHitHdrs) { + rv = compService->ReplyWithTemplate(msgHdr, replyTemplateUri, + m_msgWindow, server); + if (NS_FAILED(rv)) { + if (rv == NS_ERROR_ABORT) { + (void)curFilter->LogRuleHitFail( + filterAction, msgHdr, rv, + "filterFailureSendingReplyAborted"_ns); + } else { + (void)curFilter->LogRuleHitFail( + filterAction, msgHdr, rv, + "filterFailureSendingReplyError"_ns); + } + } + BREAK_ACTION_IF_FAILURE(rv, "ReplyWithTemplate failed"); + } + } break; + case nsMsgFilterAction::DeleteFromPop3Server: { + nsCOMPtr<nsIMsgLocalMailFolder> localFolder = + do_QueryInterface(curFolder, &rv); + BREAK_ACTION_IF_FAILURE(rv, "Current folder not a local folder"); + BREAK_ACTION_IF_FALSE(localFolder, + "Current folder not a local folder"); + // This action ignores the deleteMailLeftOnServer preference + rv = localFolder->MarkMsgsOnPop3Server(m_searchHitHdrs, + POP3_FORCE_DEL); + BREAK_ACTION_IF_FAILURE(rv, "MarkMsgsOnPop3Server failed"); + + // Delete the partial headers. They're useless now + // that the server copy is being deleted. + nsTArray<RefPtr<nsIMsgDBHdr>> partialMsgs; + for (uint32_t i = 0; i < m_searchHits.Length(); ++i) { + nsIMsgDBHdr* msgHdr = m_searchHitHdrs[i]; + nsMsgKey msgKey = m_searchHits[i]; + uint32_t flags; + msgHdr->GetFlags(&flags); + if (flags & nsMsgMessageFlags::Partial) { + partialMsgs.AppendElement(msgHdr); + m_stopFiltering.AppendElement(msgKey); + curFolder->OrProcessingFlags(msgKey, + nsMsgProcessingFlags::FilterToMove); + } + } + if (!partialMsgs.IsEmpty()) { + rv = curFolder->DeleteMessages(partialMsgs, m_msgWindow, true, + false, nullptr, false); + BREAK_ACTION_IF_FAILURE(rv, "Delete messages failed"); + } + } break; + case nsMsgFilterAction::FetchBodyFromPop3Server: { + nsCOMPtr<nsIMsgLocalMailFolder> localFolder = + do_QueryInterface(curFolder, &rv); + BREAK_ACTION_IF_FAILURE(rv, "current folder not local"); + BREAK_ACTION_IF_FALSE(localFolder, "current folder not local"); + nsTArray<RefPtr<nsIMsgDBHdr>> messages; + for (nsIMsgDBHdr* msgHdr : m_searchHitHdrs) { + uint32_t flags = 0; + msgHdr->GetFlags(&flags); + if (flags & nsMsgMessageFlags::Partial) + messages.AppendElement(msgHdr); + } + if (messages.Length() > 0) { + rv = curFolder->DownloadMessagesForOffline(messages, m_msgWindow); + BREAK_ACTION_IF_FAILURE(rv, "DownloadMessagesForOffline failed"); + } + } break; + + case nsMsgFilterAction::StopExecution: { + // don't apply any more filters + m_stopFiltering.AppendElements(m_searchHits); + m_nextAction = numActions; + } break; + + case nsMsgFilterAction::Custom: { + nsMsgFilterTypeType filterType; + curFilter->GetFilterType(&filterType); + nsCOMPtr<nsIMsgFilterCustomAction> customAction; + rv = filterAction->GetCustomAction(getter_AddRefs(customAction)); + BREAK_ACTION_IF_FAILURE(rv, "Could not get custom action"); + + nsAutoCString value; + rv = filterAction->GetStrValue(value); + BREAK_ACTION_IF_FAILURE(rv, "Could not get custom action value"); + bool isAsync = false; + customAction->GetIsAsync(&isAsync); + rv = customAction->ApplyAction(m_searchHitHdrs, value, this, + filterType, m_msgWindow); + BREAK_ACTION_IF_FAILURE(rv, "custom action failed to apply"); + if (isAsync) { + MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug, + ("(Post) Action execution continues async")); + return NS_OK; // custom action should call ApplyFilter on callback + } + } break; + + default: + NS_ERROR("unexpected filter action"); + BREAK_ACTION_IF_FAILURE(NS_ERROR_UNEXPECTED, + "Unexpected filter action"); + } + if (NS_FAILED(finalResult)) { + mFinalResult = finalResult; + MOZ_LOG(FILTERLOGMODULE, LogLevel::Error, + ("(Post) Action execution failed with error: %" PRIx32, + static_cast<uint32_t>(mFinalResult))); + if (loggingEnabled && m_searchHitHdrs.Length() > 0) { + (void)curFilter->LogRuleHitFail(filterAction, m_searchHitHdrs[0], + mFinalResult, + "filterActionFailed"_ns); + } + } else { + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("(Post) Action execution succeeded")); + } + } + } while (false); // end error management block + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("(Post) Finished executing actions")); + return RunNextFilter(); +} + +NS_IMETHODIMP nsMsgFilterService::GetTempFilterList( + nsIMsgFolder* aFolder, nsIMsgFilterList** aFilterList) { + NS_ENSURE_ARG_POINTER(aFilterList); + + nsMsgFilterList* filterList = new nsMsgFilterList; + filterList->SetFolder(aFolder); + filterList->m_temporaryList = true; + NS_ADDREF(*aFilterList = filterList); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFilterService::ApplyFiltersToFolders( + nsIMsgFilterList* aFilterList, + const nsTArray<RefPtr<nsIMsgFolder>>& aFolders, nsIMsgWindow* aMsgWindow, + nsIMsgOperationListener* aCallback) { + MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug, + ("(Post) nsMsgFilterService::ApplyFiltersToFolders")); + NS_ENSURE_ARG_POINTER(aFilterList); + + uint32_t filterCount; + aFilterList->GetFilterCount(&filterCount); + nsCString listId; + aFilterList->GetListId(listId); + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("(Post) Manual filter run initiated")); + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("(Post) Running %" PRIu32 " filters from %s on %" PRIu32 " folders", + filterCount, listId.get(), (int)aFolders.Length())); + + RefPtr<nsMsgFilterAfterTheFact> filterExecutor = + new nsMsgFilterAfterTheFact(aMsgWindow, aFilterList, aFolders, aCallback); + if (filterExecutor) + return filterExecutor->AdvanceToNextFolder(); + else + return NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP nsMsgFilterService::AddCustomAction( + nsIMsgFilterCustomAction* aAction) { + mCustomActions.AppendElement(aAction); + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilterService::GetCustomActions( + nsTArray<RefPtr<nsIMsgFilterCustomAction>>& actions) { + actions = mCustomActions.Clone(); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFilterService::GetCustomAction(const nsACString& aId, + nsIMsgFilterCustomAction** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + + for (nsIMsgFilterCustomAction* action : mCustomActions) { + nsAutoCString id; + nsresult rv = action->GetId(id); + if (NS_SUCCEEDED(rv) && aId.Equals(id)) { + NS_ADDREF(*aResult = action); + return NS_OK; + } + } + aResult = nullptr; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsMsgFilterService::AddCustomTerm(nsIMsgSearchCustomTerm* aTerm) { + mCustomTerms.AppendElement(aTerm); + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilterService::GetCustomTerms( + nsTArray<RefPtr<nsIMsgSearchCustomTerm>>& terms) { + terms = mCustomTerms.Clone(); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgFilterService::GetCustomTerm(const nsACString& aId, + nsIMsgSearchCustomTerm** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + + for (nsIMsgSearchCustomTerm* term : mCustomTerms) { + nsAutoCString id; + nsresult rv = term->GetId(id); + if (NS_SUCCEEDED(rv) && aId.Equals(id)) { + NS_ADDREF(*aResult = term); + return NS_OK; + } + } + aResult = nullptr; + // we use a null result to indicate failure to find a term + return NS_OK; +} + +/** + * Translate the filter type flag into human readable type names. + * In case of multiple flag they are delimited by '&'. + */ +NS_IMETHODIMP +nsMsgFilterService::FilterTypeName(nsMsgFilterTypeType filterType, + nsACString& typeName) { + typeName.Truncate(); + if (filterType == nsMsgFilterType::None) { + typeName.Assign("None"); + return NS_OK; + } + + if ((filterType & nsMsgFilterType::Incoming) == nsMsgFilterType::Incoming) { + typeName.Append("Incoming&"); + } else { + if ((filterType & nsMsgFilterType::Inbox) == nsMsgFilterType::Inbox) { + typeName.Append("Inbox&"); + } else { + if (filterType & nsMsgFilterType::InboxRule) + typeName.Append("InboxRule&"); + if (filterType & nsMsgFilterType::InboxJavaScript) + typeName.Append("InboxJavaScript&"); + } + if ((filterType & nsMsgFilterType::News) == nsMsgFilterType::News) { + typeName.Append("News&"); + } else { + if (filterType & nsMsgFilterType::NewsRule) typeName.Append("NewsRule&"); + if (filterType & nsMsgFilterType::NewsJavaScript) + typeName.Append("NewsJavaScript&"); + } + } + if (filterType & nsMsgFilterType::Manual) typeName.Append("Manual&"); + if (filterType & nsMsgFilterType::PostPlugin) typeName.Append("PostPlugin&"); + if (filterType & nsMsgFilterType::PostOutgoing) + typeName.Append("PostOutgoing&"); + if (filterType & nsMsgFilterType::Archive) typeName.Append("Archive&"); + if (filterType & nsMsgFilterType::Periodic) typeName.Append("Periodic&"); + + if (typeName.IsEmpty()) { + typeName.Assign("UNKNOWN"); + } else { + // Cut the trailing '&' character. + typeName.Truncate(typeName.Length() - 1); + } + return NS_OK; +} + +// nsMsgApplyFiltersToMessages overrides nsMsgFilterAfterTheFact in order to +// apply filters to a list of messages, rather than an entire folder +class nsMsgApplyFiltersToMessages : public nsMsgFilterAfterTheFact { + public: + nsMsgApplyFiltersToMessages(nsIMsgWindow* aMsgWindow, + nsIMsgFilterList* aFilterList, + const nsTArray<RefPtr<nsIMsgFolder>>& aFolderList, + const nsTArray<RefPtr<nsIMsgDBHdr>>& aMsgHdrList, + nsMsgFilterTypeType aFilterType, + nsIMsgOperationListener* aCallback); + + protected: + virtual nsresult RunNextFilter(); + + nsTArray<RefPtr<nsIMsgDBHdr>> m_msgHdrList; + nsMsgFilterTypeType m_filterType; +}; + +nsMsgApplyFiltersToMessages::nsMsgApplyFiltersToMessages( + nsIMsgWindow* aMsgWindow, nsIMsgFilterList* aFilterList, + const nsTArray<RefPtr<nsIMsgFolder>>& aFolderList, + const nsTArray<RefPtr<nsIMsgDBHdr>>& aMsgHdrList, + nsMsgFilterTypeType aFilterType, nsIMsgOperationListener* aCallback) + : nsMsgFilterAfterTheFact(aMsgWindow, aFilterList, aFolderList, aCallback), + m_msgHdrList(aMsgHdrList.Clone()), + m_filterType(aFilterType) { + MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug, + ("(Post) nsMsgApplyFiltersToMessages")); +} + +nsresult nsMsgApplyFiltersToMessages::RunNextFilter() { + MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug, + ("(Post) nsMsgApplyFiltersToMessages::RunNextFilter")); + nsresult rv = NS_OK; + while (true) { + m_curFilter = nullptr; // we are done with the current filter + if (!m_curFolder || // Not an error, we just need to run + // AdvanceToNextFolder() + m_curFilterIndex >= m_numFilters) + break; + + BREAK_IF_FALSE(m_filters, "No filters"); + nsMsgFilterTypeType filterType; + bool isEnabled; + rv = + m_filters->GetFilterAt(m_curFilterIndex++, getter_AddRefs(m_curFilter)); + CONTINUE_IF_FAILURE(rv, "Could not get filter"); + rv = m_curFilter->GetFilterType(&filterType); + CONTINUE_IF_FAILURE(rv, "Could not get filter type"); + if (!(filterType & m_filterType)) continue; + rv = m_curFilter->GetEnabled(&isEnabled); + CONTINUE_IF_FAILURE(rv, "Could not get isEnabled"); + if (!isEnabled) continue; + + MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug, + ("(Post) Running filter %" PRIu32, m_curFilterIndex)); + nsString filterName; + m_curFilter->GetFilterName(filterName); + // clang-format off + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("(Post) Filter name: %s", NS_ConvertUTF16toUTF8(filterName).get())); + // clang-format on + + nsCOMPtr<nsIMsgSearchScopeTerm> scope(new nsMsgSearchScopeTerm( + nullptr, nsMsgSearchScope::offlineMail, m_curFolder)); + BREAK_IF_FALSE(scope, "Could not create scope, OOM?"); + m_curFilter->SetScope(scope); + OnNewSearch(); + + for (auto msgHdr : m_msgHdrList) { + bool matched; + rv = m_curFilter->MatchHdr(msgHdr, m_curFolder, m_curFolderDB, + EmptyCString(), &matched); + if (NS_SUCCEEDED(rv) && matched) { + // In order to work with nsMsgFilterAfterTheFact::ApplyFilter we + // initialize nsMsgFilterAfterTheFact's information with a search hit + // now for the message that we're filtering. + OnSearchHit(msgHdr, m_curFolder); + } + } + m_curFilter->SetScope(nullptr); + + if (m_searchHits.Length() > 0) { + m_nextAction = 0; + rv = ApplyFilter(); + if (NS_SUCCEEDED(rv)) + return NS_OK; // async callback will continue, or we are done. + } + } + + if (NS_FAILED(rv)) { + // clang-format off + MOZ_LOG(FILTERLOGMODULE, LogLevel::Error, + ("(Post) Filter run failed (%" PRIx32 ")", + static_cast<uint32_t>(rv))); + // clang-format on + m_filters->LogFilterMessage(u"Filter run failed"_ns, m_curFilter); + NS_WARNING_ASSERTION(false, "Failed to run filters"); + } else { + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("(Post) Filter run finished on the current folder")); + } + + m_curFilter = nullptr; + + // We expect the failure is already recorded through one of the macro + // expressions, that will have console logging added to them. + // So an additional console warning is not needed here. + return AdvanceToNextFolder(); +} + +NS_IMETHODIMP nsMsgFilterService::ApplyFilters( + nsMsgFilterTypeType aFilterType, + const nsTArray<RefPtr<nsIMsgDBHdr>>& aMsgHdrList, nsIMsgFolder* aFolder, + nsIMsgWindow* aMsgWindow, nsIMsgOperationListener* aCallback) { + MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug, + ("(Post) nsMsgApplyFiltersToMessages::ApplyFilters")); + NS_ENSURE_ARG_POINTER(aFolder); + + nsCOMPtr<nsIMsgFilterList> filterList; + aFolder->GetFilterList(aMsgWindow, getter_AddRefs(filterList)); + NS_ENSURE_STATE(filterList); + + uint32_t filterCount; + filterList->GetFilterCount(&filterCount); + nsCString listId; + filterList->GetListId(listId); + nsString folderName; + aFolder->GetName(folderName); + nsCString typeName; + FilterTypeName(aFilterType, typeName); + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("(Post) Filter run initiated, trigger=%s (%i)", typeName.get(), + aFilterType)); + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("(Post) Running %" PRIu32 " filters from %s on %" PRIu32 + " message(s) in folder '%s'", + filterCount, listId.get(), (uint32_t)aMsgHdrList.Length(), + NS_ConvertUTF16toUTF8(folderName).get())); + + // Create our nsMsgApplyFiltersToMessages object which will be called when + // ApplyFiltersToHdr finds one or more filters that hit. + RefPtr<nsMsgApplyFiltersToMessages> filterExecutor = + new nsMsgApplyFiltersToMessages(aMsgWindow, filterList, {aFolder}, + aMsgHdrList, aFilterType, aCallback); + + if (filterExecutor) return filterExecutor->AdvanceToNextFolder(); + + return NS_ERROR_OUT_OF_MEMORY; +} + +/* void OnStartCopy (); */ +NS_IMETHODIMP nsMsgFilterAfterTheFact::OnStartCopy() { return NS_OK; } + +/* void OnProgress (in uint32_t aProgress, in uint32_t aProgressMax); */ +NS_IMETHODIMP nsMsgFilterAfterTheFact::OnProgress(uint32_t aProgress, + uint32_t aProgressMax) { + return NS_OK; +} + +/* void SetMessageKey (in uint32_t aKey); */ +NS_IMETHODIMP nsMsgFilterAfterTheFact::SetMessageKey(nsMsgKey /* aKey */) { + return NS_OK; +} + +NS_IMETHODIMP nsMsgFilterAfterTheFact::GetMessageId(nsACString& messageId) { + return NS_OK; +} + +/* void OnStopCopy (in nsresult aStatus); */ +NS_IMETHODIMP nsMsgFilterAfterTheFact::OnStopCopy(nsresult aStatus) { + if (NS_SUCCEEDED(aStatus)) { + // clang-format off + MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, + ("(Post) Async message copy from filter action finished successfully")); + // clang-format on + return ApplyFilter(); + } + MOZ_LOG(FILTERLOGMODULE, LogLevel::Error, + ("(Post) Async message copy from filter action failed (%" PRIx32 ")", + static_cast<uint32_t>(aStatus))); + + mFinalResult = aStatus; + if (m_msgWindow && !ContinueExecutionPrompt()) return OnEndExecution(); + + // Copy failed, so run the next filter + return RunNextFilter(); +} + +bool nsMsgFilterAfterTheFact::ContinueExecutionPrompt() { + if (!m_curFilter) return false; + nsCOMPtr<nsIStringBundle> bundle; + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::components::StringBundle::Service(); + if (!bundleService) return false; + bundleService->CreateBundle("chrome://messenger/locale/filter.properties", + getter_AddRefs(bundle)); + if (!bundle) return false; + nsString filterName; + m_curFilter->GetFilterName(filterName); + nsString formatString; + nsString confirmText; + AutoTArray<nsString, 1> formatStrings = {filterName}; + nsresult rv = bundle->FormatStringFromName("continueFilterExecution", + formatStrings, confirmText); + if (NS_FAILED(rv)) return false; + bool returnVal = false; + (void)DisplayConfirmationPrompt(m_msgWindow, confirmText.get(), &returnVal); + if (!returnVal) { + MOZ_LOG(FILTERLOGMODULE, LogLevel::Warning, + ("(Post) User aborted further filter execution on prompt")); + } + return returnVal; +} + +nsresult nsMsgFilterAfterTheFact::DisplayConfirmationPrompt( + nsIMsgWindow* msgWindow, const char16_t* confirmString, bool* confirmed) { + if (msgWindow) { + nsCOMPtr<nsIDocShell> docShell; + msgWindow->GetRootDocShell(getter_AddRefs(docShell)); + if (docShell) { + nsCOMPtr<nsIPrompt> dialog(do_GetInterface(docShell)); + if (dialog && confirmString) + dialog->Confirm(nullptr, confirmString, confirmed); + } + } + return NS_OK; +} diff --git a/comm/mailnews/search/src/nsMsgFilterService.h b/comm/mailnews/search/src/nsMsgFilterService.h new file mode 100644 index 0000000000..7089c8362f --- /dev/null +++ b/comm/mailnews/search/src/nsMsgFilterService.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef _nsMsgFilterService_H_ +#define _nsMsgFilterService_H_ + +#include "nsIMsgFilterService.h" +#include "nsIFile.h" +#include "nsTArray.h" + +class nsIMsgWindow; +class nsIStringBundle; + +// The filter service is used to acquire and manipulate filter lists. + +class nsMsgFilterService : public nsIMsgFilterService { + public: + nsMsgFilterService(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGFILTERSERVICE + // clients call OpenFilterList to get a handle to a FilterList, of existing + // nsMsgFilter. These are manipulated by the front end as a result of user + // interaction with dialog boxes. To apply the new list call + // MSG_CloseFilterList. + + nsresult BackUpFilterFile(nsIFile* aFilterFile, nsIMsgWindow* aMsgWindow); + nsresult AlertBackingUpFilterFile(nsIMsgWindow* aMsgWindow); + nsresult ThrowAlertMsg(const char* aMsgName, nsIMsgWindow* aMsgWindow); + nsresult GetStringFromBundle(const char* aMsgName, nsAString& aResult); + nsresult GetFilterStringBundle(nsIStringBundle** aBundle); + + protected: + virtual ~nsMsgFilterService(); + + // defined custom action list + nsTArray<RefPtr<nsIMsgFilterCustomAction>> mCustomActions; + // defined custom term list + nsTArray<RefPtr<nsIMsgSearchCustomTerm>> mCustomTerms; +}; + +#endif // _nsMsgFilterService_H_ diff --git a/comm/mailnews/search/src/nsMsgImapSearch.cpp b/comm/mailnews/search/src/nsMsgImapSearch.cpp new file mode 100644 index 0000000000..9ca0183d60 --- /dev/null +++ b/comm/mailnews/search/src/nsMsgImapSearch.cpp @@ -0,0 +1,991 @@ +/* -*- 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 "nsMsgSearchAdapter.h" +#include "nsMsgSearchScopeTerm.h" +#include "nsMsgResultElement.h" +#include "nsMsgSearchTerm.h" +#include "nsIMsgHdr.h" +#include "nsMsgSearchImap.h" +#include "prmem.h" +#include "nsIMsgImapMailFolder.h" +// Implementation of search for IMAP mail folders + +nsMsgSearchOnlineMail::nsMsgSearchOnlineMail( + nsMsgSearchScopeTerm* scope, + nsTArray<RefPtr<nsIMsgSearchTerm>> const& termList) + : nsMsgSearchAdapter(scope, termList) {} + +nsMsgSearchOnlineMail::~nsMsgSearchOnlineMail() {} + +nsresult nsMsgSearchOnlineMail::ValidateTerms() { + nsresult err = nsMsgSearchAdapter::ValidateTerms(); + + if (NS_SUCCEEDED(err)) { + // ### mwelch Figure out the charsets to use + // for the search terms and targets. + nsAutoString srcCharset, dstCharset; + GetSearchCharsets(srcCharset, dstCharset); + + // do IMAP specific validation + err = Encode(m_encoding, m_searchTerms, dstCharset.get(), m_scope); + NS_ASSERTION(NS_SUCCEEDED(err), "failed to encode imap search"); + } + + return err; +} + +NS_IMETHODIMP nsMsgSearchOnlineMail::GetEncoding(char** result) { + *result = ToNewCString(m_encoding); + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchOnlineMail::AddResultElement(nsIMsgDBHdr* pHeaders) { + nsresult err = NS_OK; + + nsCOMPtr<nsIMsgSearchSession> searchSession; + m_scope->GetSearchSession(getter_AddRefs(searchSession)); + if (searchSession) { + nsCOMPtr<nsIMsgFolder> scopeFolder; + err = m_scope->GetFolder(getter_AddRefs(scopeFolder)); + searchSession->AddSearchHit(pHeaders, scopeFolder); + } + // XXXX alecf do not checkin without fixing! + // m_scope->m_searchSession->AddResultElement (newResult); + return err; +} + +nsresult nsMsgSearchOnlineMail::Search(bool* aDone) { + // we should never end up here for a purely online + // folder. We might for an offline IMAP folder. + nsresult err = NS_ERROR_NOT_IMPLEMENTED; + + return err; +} + +nsresult nsMsgSearchOnlineMail::Encode( + nsCString& pEncoding, nsTArray<RefPtr<nsIMsgSearchTerm>> const& searchTerms, + const char16_t* destCharset, nsIMsgSearchScopeTerm* scope) { + nsCString imapTerms; + + // check if searchTerms are ascii only + bool asciiOnly = true; + // ### what's this mean in the NWO????? + + if (true) // !(srcCharset & CODESET_MASK == STATEFUL || srcCharset & + // CODESET_MASK == WIDECHAR) ) + // assume all single/multiple bytes charset has ascii as subset + { + for (nsIMsgSearchTerm* pTerm : searchTerms) { + nsMsgSearchAttribValue attribute; + pTerm->GetAttrib(&attribute); + if (IS_STRING_ATTRIBUTE(attribute)) { + nsString pchar; + nsCOMPtr<nsIMsgSearchValue> searchValue; + + nsresult rv = pTerm->GetValue(getter_AddRefs(searchValue)); + if (NS_FAILED(rv) || !searchValue) continue; + + rv = searchValue->GetStr(pchar); + if (NS_FAILED(rv) || pchar.IsEmpty()) continue; + asciiOnly = mozilla::IsAsciiNullTerminated( + static_cast<const char16_t*>(pchar.get())); + if (!asciiOnly) { + break; + } + } + } + } + // else + // asciiOnly = false; // TODO: enable this line when the condition is not a + // plain "true" in the if(). + + const char16_t* usAsciiCharSet = u"us-ascii"; + // Get the optional CHARSET parameter, in case we need it. + char* csname = GetImapCharsetParam(asciiOnly ? usAsciiCharSet : destCharset); + + // We do not need "srcCharset" since the search term in always unicode. + // I just pass destCharset for both src and dest charset instead of removing + // srcCharst from the argument. + nsresult err = nsMsgSearchAdapter::EncodeImap( + getter_Copies(imapTerms), searchTerms, + asciiOnly ? usAsciiCharSet : destCharset, + asciiOnly ? usAsciiCharSet : destCharset, false); + if (NS_SUCCEEDED(err)) { + pEncoding.AppendLiteral("SEARCH"); + if (csname) { + // We have a "CHARSET <name>" string which is typically appended to + // "SEARCH". But don't append it if server has UTF8=ACCEPT enabled. + nsCOMPtr<nsIMsgFolder> folder; + err = scope->GetFolder(getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(err, err); + nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(folder); + bool utf8AcceptEnabled = false; + imapFolder->GetShouldUseUtf8FolderName(&utf8AcceptEnabled); + if (!utf8AcceptEnabled) pEncoding.Append(csname); + } + pEncoding.Append(imapTerms); + } + PR_FREEIF(csname); + return err; +} + +// clang-format off +nsresult +nsMsgSearchValidityManager::InitOfflineMailTable() +{ + NS_ASSERTION(!m_offlineMailTable, "offline mail table already initted"); + nsresult rv = NewTable(getter_AddRefs(m_offlineMailTable)); + NS_ENSURE_SUCCESS(rv,rv); + + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); + + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1); + + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::EndsWith, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::EndsWith, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::IsInAB, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::IsInAB, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::IsntInAB, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::IsntInAB, 1); + + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::EndsWith, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::EndsWith, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::IsInAB, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsInAB, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::IsntInAB, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsntInAB, 1); + + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::EndsWith, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::EndsWith, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsInAB, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsInAB, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsntInAB, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsntInAB, 1); + + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::EndsWith, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::EndsWith, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsInAB, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsInAB, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsntInAB, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsntInAB, 1); + + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); + + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Body, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Body, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Body, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Body, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Isnt, 1); + + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); + + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsHigherThan, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsHigherThan, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsLowerThan, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsLowerThan, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Priority, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Priority, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Isnt, 1); + + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1); + + // m_offlineMailTable->SetValidButNotShown (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsHigherThan, 1); + // m_offlineMailTable->SetValidButNotShown (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLowerThan, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1); + + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsEmpty, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsEmpty, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsntEmpty, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsntEmpty, 1); + + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsGreaterThan, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsGreaterThan, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsLessThan, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsLessThan, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::Is, 1); + + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Isnt, 1); + + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::HasAttachmentStatus, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::HasAttachmentStatus, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::HasAttachmentStatus, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::HasAttachmentStatus, nsMsgSearchOp::Isnt, 1); + + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Size, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::Is, 1); + + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1); + m_offlineMailTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1); + m_offlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1); + return rv; +} + + +nsresult +nsMsgSearchValidityManager::InitOnlineMailTable() +{ + NS_ASSERTION(!m_onlineMailTable, "Online mail table already initted!"); + nsresult rv = NewTable(getter_AddRefs(m_onlineMailTable)); + NS_ENSURE_SUCCESS(rv,rv); + + m_onlineMailTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); + + m_onlineMailTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1); + + m_onlineMailTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1); + + m_onlineMailTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1); + + m_onlineMailTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); + + m_onlineMailTable->SetAvailable(nsMsgSearchAttrib::Body, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetAvailable(nsMsgSearchAttrib::Body, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::DoesntContain, 1); + + m_onlineMailTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); + m_onlineMailTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); + m_onlineMailTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); + m_onlineMailTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); + + m_onlineMailTable->SetAvailable(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1); + m_onlineMailTable->SetAvailable(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1); + + m_onlineMailTable->SetAvailable(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1); + m_onlineMailTable->SetAvailable(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1); + m_onlineMailTable->SetAvailable(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1); + + m_onlineMailTable->SetAvailable(nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); + m_onlineMailTable->SetAvailable(nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); + + m_onlineMailTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1); + + m_onlineMailTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + m_onlineMailTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + m_onlineMailTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + m_onlineMailTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + + return rv; +} + +nsresult +nsMsgSearchValidityManager::InitOnlineMailFilterTable() +{ + // Oh what a tangled web... + // + // IMAP filtering happens on the client, fundamentally using the same + // capabilities as POP filtering. However, since we don't yet have the + // IMAP message body, we can't filter on body attributes. So this table + // is supposed to be the same as offline mail, except that the body + // attribute is omitted + NS_ASSERTION(!m_onlineMailFilterTable, "online filter table already initted"); + nsresult rv = NewTable(getter_AddRefs(m_onlineMailFilterTable)); + NS_ENSURE_SUCCESS(rv,rv); + + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); + + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1); + + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::EndsWith, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::EndsWith, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::IsInAB, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::IsInAB, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::IsntInAB, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::IsntInAB, 1); + + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::EndsWith, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::EndsWith, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::IsInAB, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsInAB, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::IsntInAB, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsntInAB, 1); + + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::EndsWith, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::EndsWith, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsInAB, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsInAB, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsntInAB, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsntInAB, 1); + + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::EndsWith, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::EndsWith, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsInAB, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsInAB, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsntInAB, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsntInAB, 1); + + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); + + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); + + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsHigherThan, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsHigherThan, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsLowerThan, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsLowerThan, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Priority, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Priority, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Isnt, 1); + + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1); + + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1); + + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); + + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1); + + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + m_onlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + m_onlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + + return rv; +} + +nsresult +nsMsgSearchValidityManager::InitOfflineMailFilterTable() +{ + NS_ASSERTION(!m_offlineMailFilterTable, "offline mail filter table already initted"); + nsresult rv = NewTable(getter_AddRefs(m_offlineMailFilterTable)); + NS_ENSURE_SUCCESS(rv,rv); + + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); + + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1); + + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::EndsWith, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::EndsWith, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::IsInAB, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::IsInAB, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::IsntInAB, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::IsntInAB, 1); + + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::EndsWith, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::EndsWith, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::IsInAB, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsInAB, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::IsntInAB, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsntInAB, 1); + + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::EndsWith, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::EndsWith, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsInAB, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsInAB, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsntInAB, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsntInAB, 1); + + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::EndsWith, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::EndsWith, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsInAB, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsInAB, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsntInAB, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsntInAB, 1); + + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); + + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Body, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Body, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Body, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Body, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Isnt, 1); + + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); + + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsHigherThan, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsHigherThan, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsLowerThan, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsLowerThan, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Priority, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Priority, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Isnt, 1); + + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1); + + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1); + + // junk status and attachment status not available for offline mail (POP) filters + // because we won't know those until after the message has been analyzed. + // see bug #185937 + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Size, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::Is, 1); + + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1); + + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + m_offlineMailFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + m_offlineMailFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + + return rv; +} + +// Online Manual is used for IMAP and NEWS, where at manual +// filtering we have junk info, but cannot assure that the +// body is available. +nsresult +nsMsgSearchValidityManager::InitOnlineManualFilterTable() +{ + NS_ASSERTION(!m_onlineManualFilterTable, "online manual filter table already initted"); + nsresult rv = NewTable(getter_AddRefs(m_onlineManualFilterTable)); + NS_ENSURE_SUCCESS(rv, rv); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::BeginsWith, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::BeginsWith, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::EndsWith, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::EndsWith, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::IsInAB, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::IsInAB, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::To, nsMsgSearchOp::IsntInAB, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::To, nsMsgSearchOp::IsntInAB, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::BeginsWith, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::BeginsWith, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::EndsWith, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::EndsWith, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::IsInAB, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsInAB, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::CC, nsMsgSearchOp::IsntInAB, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::CC, nsMsgSearchOp::IsntInAB, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::BeginsWith, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::BeginsWith, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::EndsWith, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::EndsWith, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsInAB, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsInAB, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsntInAB, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::ToOrCC, nsMsgSearchOp::IsntInAB, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::BeginsWith, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::BeginsWith, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::EndsWith, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::EndsWith, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsInAB, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsInAB, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsntInAB, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::AllAddresses, nsMsgSearchOp::IsntInAB, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsHigherThan, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsHigherThan, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsLowerThan, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::IsLowerThan, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Priority, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Priority, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Priority, nsMsgSearchOp::Isnt, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsEmpty, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsEmpty, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsntEmpty, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsntEmpty, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsGreaterThan, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsGreaterThan, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsLessThan, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsLessThan, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::Is, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Isnt, 1); + + // HasAttachmentStatus does not work reliably until the user has opened a + // message to force it through MIME. We need a solution for this (bug 105169) + // but in the meantime, I'm doing the same thing here that we do in the + // offline mail table, as this does not really depend at the moment on + // whether we have downloaded the body for offline use. + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::HasAttachmentStatus, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::HasAttachmentStatus, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::HasAttachmentStatus, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::HasAttachmentStatus, nsMsgSearchOp::Isnt, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Size, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::Is, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1); + + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + m_onlineManualFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + m_onlineManualFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + + return rv; +} +// clang-format on diff --git a/comm/mailnews/search/src/nsMsgLocalSearch.cpp b/comm/mailnews/search/src/nsMsgLocalSearch.cpp new file mode 100644 index 0000000000..e43c0dd9d9 --- /dev/null +++ b/comm/mailnews/search/src/nsMsgLocalSearch.cpp @@ -0,0 +1,919 @@ +/* -*- 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/. */ + +// Implementation of db search for POP and offline IMAP mail folders + +#include "msgCore.h" +#include "nsIMsgDatabase.h" +#include "nsMsgSearchCore.h" +#include "nsMsgLocalSearch.h" +#include "nsIStreamListener.h" +#include "nsMsgSearchBoolExpression.h" +#include "nsMsgSearchTerm.h" +#include "nsMsgResultElement.h" +#include "nsIDBFolderInfo.h" +#include "nsMsgSearchValue.h" +#include "nsIMsgLocalMailFolder.h" +#include "nsIMsgWindow.h" +#include "nsIMsgHdr.h" +#include "nsIMsgFilterPlugin.h" +#include "nsMsgMessageFlags.h" +#include "nsMsgUtils.h" +#include "nsIMsgFolder.h" + +extern "C" { +extern int MK_MSG_SEARCH_STATUS; +extern int MK_MSG_CANT_SEARCH_IF_NO_SUMMARY; +extern int MK_MSG_SEARCH_HITS_NOT_IN_DB; +} + +//---------------------------------------------------------------------------- +// Class definitions for the boolean expression structure.... +//---------------------------------------------------------------------------- + +nsMsgSearchBoolExpression* nsMsgSearchBoolExpression::AddSearchTerm( + nsMsgSearchBoolExpression* aOrigExpr, nsIMsgSearchTerm* aNewTerm, + char* aEncodingStr) +// appropriately add the search term to the current expression and return a +// pointer to the new expression. The encodingStr is the IMAP/NNTP encoding +// string for newTerm. +{ + return aOrigExpr->leftToRightAddTerm(aNewTerm, aEncodingStr); +} + +nsMsgSearchBoolExpression* nsMsgSearchBoolExpression::AddExpressionTree( + nsMsgSearchBoolExpression* aOrigExpr, + nsMsgSearchBoolExpression* aExpression, bool aBoolOp) { + if (!aOrigExpr->m_term && !aOrigExpr->m_leftChild && + !aOrigExpr->m_rightChild) { + // just use the original expression tree... + // delete the original since we have a new original to use + delete aOrigExpr; + return aExpression; + } + + nsMsgSearchBoolExpression* newExpr = + new nsMsgSearchBoolExpression(aOrigExpr, aExpression, aBoolOp); + return (newExpr) ? newExpr : aOrigExpr; +} + +nsMsgSearchBoolExpression::nsMsgSearchBoolExpression() { + m_term = nullptr; + m_boolOp = nsMsgSearchBooleanOp::BooleanAND; + m_leftChild = nullptr; + m_rightChild = nullptr; +} + +nsMsgSearchBoolExpression::nsMsgSearchBoolExpression(nsIMsgSearchTerm* newTerm, + char* encodingStr) +// we are creating an expression which contains a single search term (newTerm) +// and the search term's IMAP or NNTP encoding value for online search +// expressions AND a boolean evaluation value which is used for offline search +// expressions. +{ + m_term = newTerm; + m_encodingStr = encodingStr; + m_boolOp = nsMsgSearchBooleanOp::BooleanAND; + + // this expression does not contain sub expressions + m_leftChild = nullptr; + m_rightChild = nullptr; +} + +nsMsgSearchBoolExpression::nsMsgSearchBoolExpression( + nsMsgSearchBoolExpression* expr1, nsMsgSearchBoolExpression* expr2, + nsMsgSearchBooleanOperator boolOp) +// we are creating an expression which contains two sub expressions and a +// boolean operator used to combine them. +{ + m_leftChild = expr1; + m_rightChild = expr2; + m_boolOp = boolOp; + + m_term = nullptr; +} + +nsMsgSearchBoolExpression::~nsMsgSearchBoolExpression() { + // we must recursively destroy all sub expressions before we destroy + // ourself.....We leave search terms alone! + delete m_leftChild; + delete m_rightChild; +} + +nsMsgSearchBoolExpression* nsMsgSearchBoolExpression::leftToRightAddTerm( + nsIMsgSearchTerm* newTerm, char* encodingStr) { + // we have a base case where this is the first term being added to the + // expression: + if (!m_term && !m_leftChild && !m_rightChild) { + m_term = newTerm; + m_encodingStr = encodingStr; + return this; + } + + nsMsgSearchBoolExpression* tempExpr = + new nsMsgSearchBoolExpression(newTerm, encodingStr); + if (tempExpr) // make sure creation succeeded + { + bool booleanAnd; + newTerm->GetBooleanAnd(&booleanAnd); + nsMsgSearchBoolExpression* newExpr = + new nsMsgSearchBoolExpression(this, tempExpr, booleanAnd); + if (newExpr) + return newExpr; + else + delete tempExpr; // clean up memory allocation in case of failure + } + return this; // in case we failed to create a new expression, return self +} + +// returns true or false depending on what the current expression evaluates to. +bool nsMsgSearchBoolExpression::OfflineEvaluate(nsIMsgDBHdr* msgToMatch, + const char* defaultCharset, + nsIMsgSearchScopeTerm* scope, + nsIMsgDatabase* db, + const nsACString& headers, + bool Filtering) { + bool result = true; // always default to false positives + bool isAnd; + + if (m_term) // do we contain just a search term? + { + nsMsgSearchOfflineMail::ProcessSearchTerm(msgToMatch, m_term, + defaultCharset, scope, db, + headers, Filtering, &result); + return result; + } + + // otherwise we must recursively determine the value of our sub expressions + + isAnd = (m_boolOp == nsMsgSearchBooleanOp::BooleanAND); + + if (m_leftChild) { + result = m_leftChild->OfflineEvaluate(msgToMatch, defaultCharset, scope, db, + headers, Filtering); + if ((result && !isAnd) || (!result && isAnd)) return result; + } + + // If we got this far, either there was no leftChild (which is impossible) + // or we got (FALSE and OR) or (TRUE and AND) from the first result. That + // means the outcome depends entirely on the rightChild. + if (m_rightChild) + result = m_rightChild->OfflineEvaluate(msgToMatch, defaultCharset, scope, + db, headers, Filtering); + + return result; +} + +// ### Maybe we can get rid of these because of our use of nsString??? +// constants used for online searching with IMAP/NNTP encoded search terms. +// the + 1 is to account for null terminators we add at each stage of assembling +// the expression... +const int sizeOfORTerm = + 6 + 1; // 6 bytes if we are combining two sub expressions with an OR term +const int sizeOfANDTerm = + 1 + 1; // 1 byte if we are combining two sub expressions with an AND term + +int32_t nsMsgSearchBoolExpression::CalcEncodeStrSize() +// recursively examine each sub expression and calculate a final size for the +// entire IMAP/NNTP encoding +{ + if (!m_term && (!m_leftChild || !m_rightChild)) // is the expression empty? + return 0; + if (m_term) // are we a leaf node? + return m_encodingStr.Length(); + if (m_boolOp == nsMsgSearchBooleanOp::BooleanOR) + return sizeOfORTerm + m_leftChild->CalcEncodeStrSize() + + m_rightChild->CalcEncodeStrSize(); + if (m_boolOp == nsMsgSearchBooleanOp::BooleanAND) + return sizeOfANDTerm + m_leftChild->CalcEncodeStrSize() + + m_rightChild->CalcEncodeStrSize(); + return 0; +} + +void nsMsgSearchBoolExpression::GenerateEncodeStr(nsCString* buffer) +// recursively combine sub expressions to form a single IMAP/NNTP encoded string +{ + if ((!m_term && (!m_leftChild || !m_rightChild))) // is expression empty? + return; + + if (m_term) // are we a leaf expression? + { + *buffer += m_encodingStr; + return; + } + + // add encode strings of each sub expression + if (m_boolOp == nsMsgSearchBooleanOp::BooleanOR) { + *buffer += " (OR"; + + m_leftChild->GenerateEncodeStr( + buffer); // insert left expression into the buffer + m_rightChild->GenerateEncodeStr( + buffer); // insert right expression into the buffer + + // HACK ALERT!!! if last returned character in the buffer is now a ' ' then + // we need to remove it because we don't want a ' ' to preceded the closing + // paren in the OR encoding. + uint32_t lastCharPos = buffer->Length() - 1; + if (buffer->CharAt(lastCharPos) == ' ') { + buffer->SetLength(lastCharPos); + } + + *buffer += ')'; + } else if (m_boolOp == nsMsgSearchBooleanOp::BooleanAND) { + m_leftChild->GenerateEncodeStr(buffer); // insert left expression + m_rightChild->GenerateEncodeStr(buffer); + } + return; +} + +//----------------------------------------------------------------------------- +//---------------- Adapter class for searching offline folders ---------------- +//----------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS_INHERITED(nsMsgSearchOfflineMail, nsMsgSearchAdapter, + nsIUrlListener) + +nsMsgSearchOfflineMail::nsMsgSearchOfflineMail( + nsIMsgSearchScopeTerm* scope, + nsTArray<RefPtr<nsIMsgSearchTerm>> const& termList) + : nsMsgSearchAdapter(scope, termList) {} + +nsMsgSearchOfflineMail::~nsMsgSearchOfflineMail() { + // Database should have been closed when the scope term finished. + CleanUpScope(); + NS_ASSERTION(!m_db, "db not closed"); +} + +nsresult nsMsgSearchOfflineMail::ValidateTerms() { + return nsMsgSearchAdapter::ValidateTerms(); +} + +nsresult nsMsgSearchOfflineMail::OpenSummaryFile() { + nsCOMPtr<nsIMsgDatabase> mailDB; + + nsresult err = NS_OK; + // do password protection of local cache thing. +#ifdef DOING_FOLDER_CACHE_PASSWORDS + if (m_scope->m_folder && + m_scope->m_folder->UserNeedsToAuthenticateForFolder(false) && + m_scope->m_folder->GetMaster()->PromptForHostPassword( + m_scope->m_frame->GetContext(), m_scope->m_folder) != 0) { + m_scope->m_frame->StopRunning(); + return SearchError_ScopeDone; + } +#endif + nsCOMPtr<nsIDBFolderInfo> folderInfo; + nsCOMPtr<nsIMsgFolder> scopeFolder; + err = m_scope->GetFolder(getter_AddRefs(scopeFolder)); + if (NS_SUCCEEDED(err) && scopeFolder) { + err = scopeFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), + getter_AddRefs(m_db)); + } else + return err; // not sure why m_folder wouldn't be set. + + if (NS_SUCCEEDED(err)) return NS_OK; + + if ((err == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) || + (err == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE)) { + nsCOMPtr<nsIMsgLocalMailFolder> localFolder = + do_QueryInterface(scopeFolder, &err); + if (NS_SUCCEEDED(err) && localFolder) { + nsCOMPtr<nsIMsgSearchSession> searchSession; + m_scope->GetSearchSession(getter_AddRefs(searchSession)); + if (searchSession) { + nsCOMPtr<nsIMsgWindow> searchWindow; + + searchSession->GetWindow(getter_AddRefs(searchWindow)); + searchSession->PauseSearch(); + localFolder->ParseFolder(searchWindow, this); + } + } + } else { + NS_ASSERTION(false, "unexpected error opening db"); + } + + return err; +} + +nsresult nsMsgSearchOfflineMail::MatchTermsForFilter( + nsIMsgDBHdr* msgToMatch, nsTArray<RefPtr<nsIMsgSearchTerm>> const& termList, + const char* defaultCharset, nsIMsgSearchScopeTerm* scope, + nsIMsgDatabase* db, const nsACString& headers, + nsMsgSearchBoolExpression** aExpressionTree, bool* pResult) { + return MatchTerms(msgToMatch, termList, defaultCharset, scope, db, headers, + true, aExpressionTree, pResult); +} + +// static method which matches a header against a list of search terms. +nsresult nsMsgSearchOfflineMail::MatchTermsForSearch( + nsIMsgDBHdr* msgToMatch, nsTArray<RefPtr<nsIMsgSearchTerm>> const& termList, + const char* defaultCharset, nsIMsgSearchScopeTerm* scope, + nsIMsgDatabase* db, nsMsgSearchBoolExpression** aExpressionTree, + bool* pResult) { + return MatchTerms(msgToMatch, termList, defaultCharset, scope, db, + EmptyCString(), false, aExpressionTree, pResult); +} + +nsresult nsMsgSearchOfflineMail::ConstructExpressionTree( + nsTArray<RefPtr<nsIMsgSearchTerm>> const& termList, uint32_t termCount, + uint32_t& aStartPosInList, nsMsgSearchBoolExpression** aExpressionTree) { + nsMsgSearchBoolExpression* finalExpression = *aExpressionTree; + + if (!finalExpression) finalExpression = new nsMsgSearchBoolExpression(); + + while (aStartPosInList < termCount) { + nsIMsgSearchTerm* pTerm = termList[aStartPosInList]; + NS_ASSERTION(pTerm, "couldn't get term to match"); + + bool beginsGrouping; + bool endsGrouping; + pTerm->GetBeginsGrouping(&beginsGrouping); + pTerm->GetEndsGrouping(&endsGrouping); + + if (beginsGrouping) { + // temporarily turn off the grouping for our recursive call + pTerm->SetBeginsGrouping(false); + nsMsgSearchBoolExpression* innerExpression = + new nsMsgSearchBoolExpression(); + + // the first search term in the grouping is the one that holds the + // operator for how this search term should be joined with the expressions + // to it's left. + bool booleanAnd; + pTerm->GetBooleanAnd(&booleanAnd); + + // now add this expression tree to our overall expression tree... + finalExpression = nsMsgSearchBoolExpression::AddExpressionTree( + finalExpression, innerExpression, booleanAnd); + + // recursively process this inner expression + ConstructExpressionTree(termList, termCount, aStartPosInList, + &finalExpression->m_rightChild); + + // undo our damage + pTerm->SetBeginsGrouping(true); + + } else { + finalExpression = nsMsgSearchBoolExpression::AddSearchTerm( + finalExpression, pTerm, + nullptr); // add the term to the expression tree + + if (endsGrouping) break; + } + + aStartPosInList++; + } // while we still have terms to process in this group + + *aExpressionTree = finalExpression; + + return NS_OK; +} + +nsresult nsMsgSearchOfflineMail::ProcessSearchTerm( + nsIMsgDBHdr* msgToMatch, nsIMsgSearchTerm* aTerm, + const char* defaultCharset, nsIMsgSearchScopeTerm* scope, + nsIMsgDatabase* db, const nsACString& headers, bool Filtering, + bool* pResult) { + nsresult err = NS_OK; + nsCString recipients; + nsCString ccList; + nsCString matchString; + nsCString msgCharset; + const char* charset; + bool charsetOverride = false; /* XXX BUG 68706 */ + uint32_t msgFlags; + bool result; + bool matchAll; + + NS_ENSURE_ARG_POINTER(pResult); + + aTerm->GetMatchAll(&matchAll); + if (matchAll) { + *pResult = true; + return NS_OK; + } + *pResult = false; + + nsMsgSearchAttribValue attrib; + aTerm->GetAttrib(&attrib); + msgToMatch->GetCharset(getter_Copies(msgCharset)); + charset = msgCharset.get(); + if (!charset || !*charset) charset = (const char*)defaultCharset; + msgToMatch->GetFlags(&msgFlags); + + switch (attrib) { + case nsMsgSearchAttrib::Sender: + msgToMatch->GetAuthor(getter_Copies(matchString)); + err = aTerm->MatchRfc822String(matchString, charset, &result); + break; + case nsMsgSearchAttrib::Subject: { + msgToMatch->GetSubject(matchString /* , true */); + if (msgFlags & nsMsgMessageFlags::HasRe) { + // Make sure we pass along the "Re: " part of the subject if this is a + // reply. + nsCString reString; + reString.AssignLiteral("Re: "); + reString.Append(matchString); + err = aTerm->MatchRfc2047String(reString, charset, charsetOverride, + &result); + } else + err = aTerm->MatchRfc2047String(matchString, charset, charsetOverride, + &result); + break; + } + case nsMsgSearchAttrib::ToOrCC: { + bool boolKeepGoing; + aTerm->GetMatchAllBeforeDeciding(&boolKeepGoing); + msgToMatch->GetRecipients(getter_Copies(recipients)); + err = aTerm->MatchRfc822String(recipients, charset, &result); + if (boolKeepGoing == result) { + msgToMatch->GetCcList(getter_Copies(ccList)); + err = aTerm->MatchRfc822String(ccList, charset, &result); + } + break; + } + case nsMsgSearchAttrib::AllAddresses: { + bool boolKeepGoing; + aTerm->GetMatchAllBeforeDeciding(&boolKeepGoing); + msgToMatch->GetRecipients(getter_Copies(recipients)); + err = aTerm->MatchRfc822String(recipients, charset, &result); + if (boolKeepGoing == result) { + msgToMatch->GetCcList(getter_Copies(ccList)); + err = aTerm->MatchRfc822String(ccList, charset, &result); + } + if (boolKeepGoing == result) { + msgToMatch->GetAuthor(getter_Copies(matchString)); + err = aTerm->MatchRfc822String(matchString, charset, &result); + } + if (boolKeepGoing == result) { + nsCString bccList; + msgToMatch->GetBccList(getter_Copies(bccList)); + err = aTerm->MatchRfc822String(bccList, charset, &result); + } + break; + } + case nsMsgSearchAttrib::Body: { + uint64_t messageOffset; + uint32_t lineCount; + msgToMatch->GetMessageOffset(&messageOffset); + msgToMatch->GetLineCount(&lineCount); + err = aTerm->MatchBody(scope, messageOffset, lineCount, charset, + msgToMatch, db, &result); + break; + } + case nsMsgSearchAttrib::Date: { + PRTime date; + msgToMatch->GetDate(&date); + err = aTerm->MatchDate(date, &result); + + break; + } + case nsMsgSearchAttrib::HasAttachmentStatus: + case nsMsgSearchAttrib::MsgStatus: + err = aTerm->MatchStatus(msgFlags, &result); + break; + case nsMsgSearchAttrib::Priority: { + nsMsgPriorityValue msgPriority; + msgToMatch->GetPriority(&msgPriority); + err = aTerm->MatchPriority(msgPriority, &result); + break; + } + case nsMsgSearchAttrib::Size: { + uint32_t messageSize; + msgToMatch->GetMessageSize(&messageSize); + err = aTerm->MatchSize(messageSize, &result); + break; + } + case nsMsgSearchAttrib::To: + msgToMatch->GetRecipients(getter_Copies(recipients)); + err = aTerm->MatchRfc822String(recipients, charset, &result); + break; + case nsMsgSearchAttrib::CC: + msgToMatch->GetCcList(getter_Copies(ccList)); + err = aTerm->MatchRfc822String(ccList, charset, &result); + break; + case nsMsgSearchAttrib::AgeInDays: { + PRTime date; + msgToMatch->GetDate(&date); + err = aTerm->MatchAge(date, &result); + break; + } + case nsMsgSearchAttrib::Keywords: { + nsCString keywords; + msgToMatch->GetStringProperty("keywords", keywords); + err = aTerm->MatchKeyword(keywords, &result); + break; + } + case nsMsgSearchAttrib::JunkStatus: { + nsCString junkScoreStr; + msgToMatch->GetStringProperty("junkscore", junkScoreStr); + err = aTerm->MatchJunkStatus(junkScoreStr.get(), &result); + break; + } + case nsMsgSearchAttrib::JunkPercent: { + // When the junk status is set by the plugin, use junkpercent (if + // available) Otherwise, use the limits (0 or 100) depending on the + // junkscore. + uint32_t junkPercent; + nsresult rv; + nsCString junkScoreOriginStr; + nsCString junkPercentStr; + msgToMatch->GetStringProperty("junkscoreorigin", junkScoreOriginStr); + msgToMatch->GetStringProperty("junkpercent", junkPercentStr); + if (junkScoreOriginStr.EqualsLiteral("plugin") && + !junkPercentStr.IsEmpty()) { + junkPercent = junkPercentStr.ToInteger(&rv); + NS_ENSURE_SUCCESS(rv, rv); + } else { + nsCString junkScoreStr; + msgToMatch->GetStringProperty("junkscore", junkScoreStr); + // When junk status is not set (uncertain) we'll set the value to ham. + if (junkScoreStr.IsEmpty()) + junkPercent = nsIJunkMailPlugin::IS_HAM_SCORE; + else { + junkPercent = junkScoreStr.ToInteger(&rv); + NS_ENSURE_SUCCESS(rv, rv); + } + } + err = aTerm->MatchJunkPercent(junkPercent, &result); + break; + } + case nsMsgSearchAttrib::JunkScoreOrigin: { + nsCString junkScoreOriginStr; + msgToMatch->GetStringProperty("junkscoreorigin", junkScoreOriginStr); + err = aTerm->MatchJunkScoreOrigin(junkScoreOriginStr.get(), &result); + break; + } + case nsMsgSearchAttrib::HdrProperty: { + err = aTerm->MatchHdrProperty(msgToMatch, &result); + break; + } + case nsMsgSearchAttrib::Uint32HdrProperty: { + err = aTerm->MatchUint32HdrProperty(msgToMatch, &result); + break; + } + case nsMsgSearchAttrib::Custom: { + err = aTerm->MatchCustom(msgToMatch, &result); + break; + } + case nsMsgSearchAttrib::FolderFlag: { + err = aTerm->MatchFolderFlag(msgToMatch, &result); + break; + } + default: + // XXX todo + // for the temporary return receipts filters, we use a custom header for + // Content-Type but unlike the other custom headers, this one doesn't show + // up in the search / filter UI. we set the attrib to be + // nsMsgSearchAttrib::OtherHeader, where as for user defined custom + // headers start at nsMsgSearchAttrib::OtherHeader + 1 Not sure if there + // is a better way to do this yet. Maybe reserve the last custom header + // for ::Content-Type? But if we do, make sure that change doesn't cause + // nsMsgFilter::GetTerm() to change, and start making us ask IMAP servers + // for the Content-Type header on all messages. + if (attrib >= nsMsgSearchAttrib::OtherHeader && + attrib < nsMsgSearchAttrib::kNumMsgSearchAttributes) { + uint32_t lineCount; + msgToMatch->GetLineCount(&lineCount); + uint64_t messageOffset; + msgToMatch->GetMessageOffset(&messageOffset); + err = aTerm->MatchArbitraryHeader(scope, lineCount, charset, + charsetOverride, msgToMatch, db, + headers, Filtering, &result); + } else { + err = NS_ERROR_INVALID_ARG; // ### was SearchError_InvalidAttribute + result = false; + } + } + + *pResult = result; + return err; +} + +nsresult nsMsgSearchOfflineMail::MatchTerms( + nsIMsgDBHdr* msgToMatch, nsTArray<RefPtr<nsIMsgSearchTerm>> const& termList, + const char* defaultCharset, nsIMsgSearchScopeTerm* scope, + nsIMsgDatabase* db, const nsACString& headers, bool Filtering, + nsMsgSearchBoolExpression** aExpressionTree, bool* pResult) { + NS_ENSURE_ARG(aExpressionTree); + nsresult err; + + if (!*aExpressionTree) { + uint32_t initialPos = 0; + uint32_t count = termList.Length(); + err = ConstructExpressionTree(termList, count, initialPos, aExpressionTree); + if (NS_FAILED(err)) return err; + } + + // evaluate the expression tree and return the result + *pResult = (*aExpressionTree) + ? (*aExpressionTree) + ->OfflineEvaluate(msgToMatch, defaultCharset, scope, db, + headers, Filtering) + : true; // vacuously true... + + return NS_OK; +} + +nsresult nsMsgSearchOfflineMail::Search(bool* aDone) { + nsresult err = NS_OK; + + NS_ENSURE_ARG(aDone); + nsresult dbErr = NS_OK; + nsMsgSearchBoolExpression* expressionTree = nullptr; + + const uint32_t kTimeSliceInMS = 200; + + *aDone = false; + // Try to open the DB lazily. This will set up a parser if one is required + if (!m_db) err = OpenSummaryFile(); + if (!m_db) // must be reparsing. + return err; + + // Reparsing is unnecessary or completed + if (NS_SUCCEEDED(err)) { + if (!m_listContext) + dbErr = m_db->ReverseEnumerateMessages(getter_AddRefs(m_listContext)); + if (NS_SUCCEEDED(dbErr) && m_listContext) { + PRIntervalTime startTime = PR_IntervalNow(); + while (!*aDone) // we'll break out of the loop after kTimeSliceInMS + // milliseconds + { + nsCOMPtr<nsIMsgDBHdr> msgDBHdr; + dbErr = m_listContext->GetNext(getter_AddRefs(msgDBHdr)); + if (NS_FAILED(dbErr)) + *aDone = true; // ###phil dbErr is dropped on the floor. just note + // that we did have an error so we'll clean up later + else { + bool match = false; + nsAutoString nullCharset, folderCharset; + GetSearchCharsets(nullCharset, folderCharset); + NS_ConvertUTF16toUTF8 charset(folderCharset); + // Is this message a hit? + err = MatchTermsForSearch(msgDBHdr, m_searchTerms, charset.get(), + m_scope, m_db, &expressionTree, &match); + // Add search hits to the results list + if (NS_SUCCEEDED(err) && match) { + AddResultElement(msgDBHdr); + } + PRIntervalTime elapsedTime = PR_IntervalNow() - startTime; + // check if more than kTimeSliceInMS milliseconds have elapsed in this + // time slice started + if (PR_IntervalToMilliseconds(elapsedTime) > kTimeSliceInMS) break; + } + } + } + } else + *aDone = true; // we couldn't open up the DB. This is an unrecoverable + // error so mark the scope as done. + + delete expressionTree; + + // in the past an error here would cause an "infinite" search because the url + // would continue to run... i.e. if we couldn't open the database, it returns + // an error code but the caller of this function says, oh, we did not finish + // so continue...what we really want is to treat this current scope as done + if (*aDone) CleanUpScope(); // Do clean up for end-of-scope processing + return err; +} + +void nsMsgSearchOfflineMail::CleanUpScope() { + // Let go of the DB when we're done with it so we don't kill the db cache + if (m_db) { + m_listContext = nullptr; + m_db->Close(false); + } + m_db = nullptr; + + if (m_scope) m_scope->CloseInputStream(); +} + +NS_IMETHODIMP nsMsgSearchOfflineMail::AddResultElement(nsIMsgDBHdr* pHeaders) { + nsresult err = NS_OK; + + nsCOMPtr<nsIMsgSearchSession> searchSession; + m_scope->GetSearchSession(getter_AddRefs(searchSession)); + if (searchSession) { + nsCOMPtr<nsIMsgFolder> scopeFolder; + err = m_scope->GetFolder(getter_AddRefs(scopeFolder)); + searchSession->AddSearchHit(pHeaders, scopeFolder); + } + return err; +} + +NS_IMETHODIMP +nsMsgSearchOfflineMail::Abort() { + // Let go of the DB when we're done with it so we don't kill the db cache + if (m_db) m_db->Close(true /* commit in case we downloaded new headers */); + m_db = nullptr; + return nsMsgSearchAdapter::Abort(); +} + +/* void OnStartRunningUrl (in nsIURI url); */ +NS_IMETHODIMP nsMsgSearchOfflineMail::OnStartRunningUrl(nsIURI* url) { + return NS_OK; +} + +/* void OnStopRunningUrl (in nsIURI url, in nsresult aExitCode); */ +NS_IMETHODIMP nsMsgSearchOfflineMail::OnStopRunningUrl(nsIURI* url, + nsresult aExitCode) { + nsCOMPtr<nsIMsgSearchSession> searchSession; + if (m_scope) m_scope->GetSearchSession(getter_AddRefs(searchSession)); + if (searchSession) searchSession->ResumeSearch(); + + return NS_OK; +} + +nsMsgSearchOfflineNews::nsMsgSearchOfflineNews( + nsIMsgSearchScopeTerm* scope, + nsTArray<RefPtr<nsIMsgSearchTerm>> const& termList) + : nsMsgSearchOfflineMail(scope, termList) {} + +nsMsgSearchOfflineNews::~nsMsgSearchOfflineNews() {} + +nsresult nsMsgSearchOfflineNews::OpenSummaryFile() { + nsresult err = NS_OK; + nsCOMPtr<nsIDBFolderInfo> folderInfo; + nsCOMPtr<nsIMsgFolder> scopeFolder; + err = m_scope->GetFolder(getter_AddRefs(scopeFolder)); + // code here used to check if offline store existed, which breaks offline + // news. + if (NS_SUCCEEDED(err) && scopeFolder) + err = scopeFolder->GetMsgDatabase(getter_AddRefs(m_db)); + return err; +} + +nsresult nsMsgSearchOfflineNews::ValidateTerms() { + return nsMsgSearchOfflineMail::ValidateTerms(); +} + +// local helper functions to set subsets of the validity table +// clang-format off +nsresult SetJunk(nsIMsgSearchValidityTable *aTable) { + NS_ENSURE_ARG_POINTER(aTable); + + aTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Is, 1); + aTable->SetEnabled (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Is, 1); + aTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Isnt, 1); + aTable->SetEnabled (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::Isnt, 1); + aTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsEmpty, 1); + aTable->SetEnabled (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsEmpty, 1); + aTable->SetAvailable(nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsntEmpty, 1); + aTable->SetEnabled (nsMsgSearchAttrib::JunkStatus, nsMsgSearchOp::IsntEmpty, 1); + + aTable->SetAvailable(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsGreaterThan, 1); + aTable->SetEnabled (nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsGreaterThan, 1); + aTable->SetAvailable(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsLessThan, 1); + aTable->SetEnabled (nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::IsLessThan, 1); + aTable->SetAvailable(nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::Is, 1); + aTable->SetEnabled (nsMsgSearchAttrib::JunkPercent, nsMsgSearchOp::Is, 1); + + aTable->SetAvailable(nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Is, 1); + aTable->SetEnabled (nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Is, 1); + aTable->SetAvailable(nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Isnt, 1); + aTable->SetEnabled (nsMsgSearchAttrib::JunkScoreOrigin, nsMsgSearchOp::Isnt, 1); + + return NS_OK; +} + +nsresult SetBody(nsIMsgSearchValidityTable* aTable) { + NS_ENSURE_ARG_POINTER(aTable); + + aTable->SetAvailable(nsMsgSearchAttrib::Body, nsMsgSearchOp::Contains, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Contains, 1); + aTable->SetAvailable(nsMsgSearchAttrib::Body, nsMsgSearchOp::DoesntContain, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::DoesntContain, 1); + aTable->SetAvailable(nsMsgSearchAttrib::Body, nsMsgSearchOp::Is, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Is, 1); + aTable->SetAvailable(nsMsgSearchAttrib::Body, nsMsgSearchOp::Isnt, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Body, nsMsgSearchOp::Isnt, 1); + + return NS_OK; +} + +// set the base validity table values for local news +nsresult SetLocalNews(nsIMsgSearchValidityTable* aTable) { + NS_ENSURE_ARG_POINTER(aTable); + + aTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + aTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); + aTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1); + aTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1); + aTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); + aTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); + aTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1); + aTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1); + + aTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + aTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); + aTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); + aTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1); + aTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); + aTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); + + aTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); + aTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); + aTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); + aTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); + + aTable->SetAvailable(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1); + aTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsGreaterThan, 1); + aTable->SetAvailable(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1); + aTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::IsLessThan, 1); + aTable->SetAvailable(nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1); + aTable->SetEnabled (nsMsgSearchAttrib::AgeInDays, nsMsgSearchOp::Is, 1); + + aTable->SetAvailable(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1); + aTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Is, 1); + aTable->SetAvailable(nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1); + aTable->SetEnabled (nsMsgSearchAttrib::MsgStatus, nsMsgSearchOp::Isnt, 1); + + aTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Contains, 1); + aTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1); + aTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1); + aTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1); + aTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1); + aTable->SetAvailable(nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1); + aTable->SetEnabled (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1); + + aTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + aTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + aTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1); + aTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1); + aTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + aTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + aTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1); + aTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1); + aTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + aTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + aTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + aTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + return NS_OK; +} +// clang-format on + +nsresult nsMsgSearchValidityManager::InitLocalNewsTable() { + NS_ASSERTION(nullptr == m_localNewsTable, + "already have local news validity table"); + nsresult rv = NewTable(getter_AddRefs(m_localNewsTable)); + NS_ENSURE_SUCCESS(rv, rv); + return SetLocalNews(m_localNewsTable); +} + +nsresult nsMsgSearchValidityManager::InitLocalNewsBodyTable() { + NS_ASSERTION(nullptr == m_localNewsBodyTable, + "already have local news+body validity table"); + nsresult rv = NewTable(getter_AddRefs(m_localNewsBodyTable)); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetLocalNews(m_localNewsBodyTable); + NS_ENSURE_SUCCESS(rv, rv); + return SetBody(m_localNewsBodyTable); +} + +nsresult nsMsgSearchValidityManager::InitLocalNewsJunkTable() { + NS_ASSERTION(nullptr == m_localNewsJunkTable, + "already have local news+junk validity table"); + nsresult rv = NewTable(getter_AddRefs(m_localNewsJunkTable)); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetLocalNews(m_localNewsJunkTable); + NS_ENSURE_SUCCESS(rv, rv); + return SetJunk(m_localNewsJunkTable); +} + +nsresult nsMsgSearchValidityManager::InitLocalNewsJunkBodyTable() { + NS_ASSERTION(nullptr == m_localNewsJunkBodyTable, + "already have local news+junk+body validity table"); + nsresult rv = NewTable(getter_AddRefs(m_localNewsJunkBodyTable)); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetLocalNews(m_localNewsJunkBodyTable); + NS_ENSURE_SUCCESS(rv, rv); + rv = SetJunk(m_localNewsJunkBodyTable); + NS_ENSURE_SUCCESS(rv, rv); + return SetBody(m_localNewsJunkBodyTable); +} diff --git a/comm/mailnews/search/src/nsMsgLocalSearch.h b/comm/mailnews/search/src/nsMsgLocalSearch.h new file mode 100644 index 0000000000..4deb5b4065 --- /dev/null +++ b/comm/mailnews/search/src/nsMsgLocalSearch.h @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef _nsMsgLocalSearch_H +#define _nsMsgLocalSearch_H + +// inherit interface here +#include "mozilla/Attributes.h" +#include "nsIMsgSearchAdapter.h" +#include "nsIUrlListener.h" + +// inherit base implementation +#include "nsMsgSearchAdapter.h" + +class nsIMsgDBHdr; +class nsIMsgSearchScopeTerm; +class nsIMsgFolder; +class nsMsgSearchBoolExpression; + +class nsMsgSearchOfflineMail : public nsMsgSearchAdapter, + public nsIUrlListener { + public: + nsMsgSearchOfflineMail(nsIMsgSearchScopeTerm*, + nsTArray<RefPtr<nsIMsgSearchTerm>> const&); + + NS_DECL_ISUPPORTS_INHERITED + + NS_DECL_NSIURLLISTENER + + NS_IMETHOD ValidateTerms() override; + NS_IMETHOD Search(bool* aDone) override; + NS_IMETHOD Abort() override; + NS_IMETHOD AddResultElement(nsIMsgDBHdr*) override; + + static nsresult MatchTermsForFilter( + nsIMsgDBHdr* msgToMatch, + nsTArray<RefPtr<nsIMsgSearchTerm>> const& termList, + const char* defaultCharset, nsIMsgSearchScopeTerm* scope, + nsIMsgDatabase* db, const nsACString& headers, + nsMsgSearchBoolExpression** aExpressionTree, bool* pResult); + + static nsresult MatchTermsForSearch( + nsIMsgDBHdr* msgTomatch, + nsTArray<RefPtr<nsIMsgSearchTerm>> const& termList, + const char* defaultCharset, nsIMsgSearchScopeTerm* scope, + nsIMsgDatabase* db, nsMsgSearchBoolExpression** aExpressionTree, + bool* pResult); + + virtual nsresult OpenSummaryFile(); + + static nsresult ProcessSearchTerm(nsIMsgDBHdr* msgToMatch, + nsIMsgSearchTerm* aTerm, + const char* defaultCharset, + nsIMsgSearchScopeTerm* scope, + nsIMsgDatabase* db, + const nsACString& headers, bool Filtering, + bool* pResult); + + protected: + virtual ~nsMsgSearchOfflineMail(); + static nsresult MatchTerms(nsIMsgDBHdr* msgToMatch, + nsTArray<RefPtr<nsIMsgSearchTerm>> const& termList, + const char* defaultCharset, + nsIMsgSearchScopeTerm* scope, nsIMsgDatabase* db, + const nsACString& headers, bool ForFilters, + nsMsgSearchBoolExpression** aExpressionTree, + bool* pResult); + + static nsresult ConstructExpressionTree( + nsTArray<RefPtr<nsIMsgSearchTerm>> const& termList, uint32_t termCount, + uint32_t& aStartPosInList, nsMsgSearchBoolExpression** aExpressionTree); + + nsCOMPtr<nsIMsgDatabase> m_db; + nsCOMPtr<nsIMsgEnumerator> m_listContext; + void CleanUpScope(); +}; + +class nsMsgSearchOfflineNews : public nsMsgSearchOfflineMail { + public: + nsMsgSearchOfflineNews(nsIMsgSearchScopeTerm*, + nsTArray<RefPtr<nsIMsgSearchTerm>> const&); + virtual ~nsMsgSearchOfflineNews(); + NS_IMETHOD ValidateTerms() override; + + virtual nsresult OpenSummaryFile() override; +}; + +#endif 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; +} diff --git a/comm/mailnews/search/src/nsMsgSearchImap.h b/comm/mailnews/search/src/nsMsgSearchImap.h new file mode 100644 index 0000000000..92dbed8dcc --- /dev/null +++ b/comm/mailnews/search/src/nsMsgSearchImap.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef _nsMsgSearchImap_h__ +# include "mozilla/Attributes.h" +# include "nsMsgSearchAdapter.h" + +//----------------------------------------------------------------------------- +//---------- Adapter class for searching online (IMAP) folders ---------------- +//----------------------------------------------------------------------------- + +class nsMsgSearchOnlineMail : public nsMsgSearchAdapter { + public: + nsMsgSearchOnlineMail(nsMsgSearchScopeTerm* scope, + nsTArray<RefPtr<nsIMsgSearchTerm>> const& termList); + virtual ~nsMsgSearchOnlineMail(); + + NS_IMETHOD ValidateTerms() override; + NS_IMETHOD Search(bool* aDone) override; + NS_IMETHOD GetEncoding(char** result) override; + NS_IMETHOD AddResultElement(nsIMsgDBHdr*) override; + + static nsresult Encode(nsCString& ppEncoding, + nsTArray<RefPtr<nsIMsgSearchTerm>> const& searchTerms, + const char16_t* destCharset, + nsIMsgSearchScopeTerm* scope); + + protected: + nsCString m_encoding; +}; + +#endif diff --git a/comm/mailnews/search/src/nsMsgSearchNews.cpp b/comm/mailnews/search/src/nsMsgSearchNews.cpp new file mode 100644 index 0000000000..022a80e79f --- /dev/null +++ b/comm/mailnews/search/src/nsMsgSearchNews.cpp @@ -0,0 +1,452 @@ +/* -*- 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 "nsMsgSearchAdapter.h" +#include "nsUnicharUtils.h" +#include "nsMsgSearchScopeTerm.h" +#include "nsMsgResultElement.h" +#include "nsMsgSearchTerm.h" +#include "nsIMsgHdr.h" +#include "nsMsgSearchNews.h" +#include "nsIDBFolderInfo.h" +#include "prprf.h" +#include "nsIMsgDatabase.h" +#include "nsMemory.h" +#include <ctype.h> + +// Implementation of search for IMAP mail folders + +// Implementation of search for newsgroups + +//----------------------------------------------------------------------------- +//----------- Adapter class for searching XPAT-capable news servers ----------- +//----------------------------------------------------------------------------- + +const char* nsMsgSearchNews::m_kNntpFrom = "FROM "; +const char* nsMsgSearchNews::m_kNntpSubject = "SUBJECT "; +const char* nsMsgSearchNews::m_kTermSeparator = "/"; + +nsMsgSearchNews::nsMsgSearchNews( + nsMsgSearchScopeTerm* scope, + nsTArray<RefPtr<nsIMsgSearchTerm>> const& termList) + : nsMsgSearchAdapter(scope, termList) { + m_searchType = ST_UNINITIALIZED; +} + +nsMsgSearchNews::~nsMsgSearchNews() {} + +nsresult nsMsgSearchNews::ValidateTerms() { + nsresult err = nsMsgSearchAdapter::ValidateTerms(); + if (NS_OK == err) { + err = Encode(&m_encoding); + } + + return err; +} + +nsresult nsMsgSearchNews::Search(bool* aDone) { + // the state machine runs in the news: handler + nsresult err = NS_ERROR_NOT_IMPLEMENTED; + return err; +} + +char16_t* nsMsgSearchNews::EncodeToWildmat(const char16_t* value) { + // Here we take advantage of XPAT's use of the wildmat format, which allows + // a case-insensitive match by specifying each case possibility for each + // character So, "FooBar" is encoded as "[Ff][Oo][Bb][Aa][Rr]" + + char16_t* caseInsensitiveValue = + (char16_t*)moz_xmalloc(sizeof(char16_t) * ((4 * NS_strlen(value)) + 1)); + if (caseInsensitiveValue) { + char16_t* walkValue = caseInsensitiveValue; + while (*value) { + if (isalpha(*value)) { + *walkValue++ = (char16_t)'['; + *walkValue++ = ToUpperCase((char16_t)*value); + *walkValue++ = ToLowerCase((char16_t)*value); + *walkValue++ = (char16_t)']'; + } else + *walkValue++ = *value; + value++; + } + *walkValue = 0; + } + return caseInsensitiveValue; +} + +char* nsMsgSearchNews::EncodeTerm(nsIMsgSearchTerm* term) { + // Develop an XPAT-style encoding for the search term + + NS_ASSERTION(term, "null term"); + if (!term) return nullptr; + + // Find a string to represent the attribute + const char* attribEncoding = nullptr; + nsMsgSearchAttribValue attrib; + + term->GetAttrib(&attrib); + + switch (attrib) { + case nsMsgSearchAttrib::Sender: + attribEncoding = m_kNntpFrom; + break; + case nsMsgSearchAttrib::Subject: + attribEncoding = m_kNntpSubject; + break; + default: + nsCString header; + term->GetArbitraryHeader(header); + if (header.IsEmpty()) { + NS_ASSERTION(false, "malformed search"); // malformed search term? + return nullptr; + } + attribEncoding = header.get(); + } + + // Build a string to represent the string pattern + bool leadingStar = false; + bool trailingStar = false; + nsMsgSearchOpValue op; + term->GetOp(&op); + + switch (op) { + case nsMsgSearchOp::Contains: + leadingStar = true; + trailingStar = true; + break; + case nsMsgSearchOp::Is: + break; + case nsMsgSearchOp::BeginsWith: + trailingStar = true; + break; + case nsMsgSearchOp::EndsWith: + leadingStar = true; + break; + default: + NS_ASSERTION(false, "malformed search"); // malformed search term? + return nullptr; + } + + // ### i18N problem Get the csid from FE, which is the correct csid for term + // int16 wincsid = INTL_GetCharSetID(INTL_DefaultTextWidgetCsidSel); + + // Do INTL_FormatNNTPXPATInRFC1522Format trick for non-ASCII string + // unsigned char *intlNonRFC1522Value = INTL_FormatNNTPXPATInNonRFC1522Format + // (wincsid, (unsigned char*)term->m_value.u.string); + nsCOMPtr<nsIMsgSearchValue> searchValue; + + nsresult rv = term->GetValue(getter_AddRefs(searchValue)); + if (NS_FAILED(rv) || !searchValue) return nullptr; + + nsString intlNonRFC1522Value; + rv = searchValue->GetStr(intlNonRFC1522Value); + if (NS_FAILED(rv) || intlNonRFC1522Value.IsEmpty()) return nullptr; + + char16_t* caseInsensitiveValue = EncodeToWildmat(intlNonRFC1522Value.get()); + if (!caseInsensitiveValue) return nullptr; + + // TO DO: Do INTL_FormatNNTPXPATInRFC1522Format trick for non-ASCII string + // Unfortunately, we currently do not handle xxx or xxx search in XPAT + // Need to add the INTL_FormatNNTPXPATInRFC1522Format call after we can do + // that so we should search a string in either RFC1522 format and non-RFC1522 + // format + + char16_t* escapedValue = EscapeSearchUrl(caseInsensitiveValue); + free(caseInsensitiveValue); + if (!escapedValue) return nullptr; + + nsAutoCString pattern; + + if (leadingStar) pattern.Append('*'); + pattern.Append(NS_ConvertUTF16toUTF8(escapedValue)); + if (trailingStar) pattern.Append('*'); + + // Combine the XPAT command syntax with the attribute and the pattern to + // form the term encoding + const char xpatTemplate[] = "XPAT %s 1- %s"; + int termLength = (sizeof(xpatTemplate) - 1) + strlen(attribEncoding) + + pattern.Length() + 1; + char* termEncoding = new char[termLength]; + if (termEncoding) + PR_snprintf(termEncoding, termLength, xpatTemplate, attribEncoding, + pattern.get()); + + return termEncoding; +} + +nsresult nsMsgSearchNews::GetEncoding(char** result) { + NS_ENSURE_ARG(result); + *result = ToNewCString(m_encoding); + return (*result) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +nsresult nsMsgSearchNews::Encode(nsCString* outEncoding) { + NS_ASSERTION(outEncoding, "no out encoding"); + if (!outEncoding) return NS_ERROR_NULL_POINTER; + + nsresult err = NS_OK; + + uint32_t numTerms = m_searchTerms.Length(); + + char** intermediateEncodings = new char*[numTerms]; + if (intermediateEncodings) { + // Build an XPAT command for each term + int encodingLength = 0; + for (uint32_t i = 0; i < numTerms; i++) { + nsIMsgSearchTerm* pTerm = m_searchTerms[i]; + // set boolean OR term if any of the search terms are an OR...this only + // works if we are using homogeneous boolean operators. + bool isBooleanOpAnd; + pTerm->GetBooleanAnd(&isBooleanOpAnd); + m_searchType = isBooleanOpAnd ? ST_AND_SEARCH : ST_OR_SEARCH; + + intermediateEncodings[i] = EncodeTerm(pTerm); + if (intermediateEncodings[i]) + encodingLength += + strlen(intermediateEncodings[i]) + strlen(m_kTermSeparator); + } + encodingLength += strlen("?search"); + // Combine all the term encodings into one big encoding + char* encoding = new char[encodingLength + 1]; + if (encoding) { + PL_strcpy(encoding, "?search"); + + for (uint32_t i = 0; i < numTerms; i++) { + if (intermediateEncodings[i]) { + PL_strcat(encoding, m_kTermSeparator); + PL_strcat(encoding, intermediateEncodings[i]); + delete[] intermediateEncodings[i]; + } + } + *outEncoding = encoding; + } else + err = NS_ERROR_OUT_OF_MEMORY; + } else + err = NS_ERROR_OUT_OF_MEMORY; + delete[] intermediateEncodings; + + return err; +} + +NS_IMETHODIMP nsMsgSearchNews::AddHit(nsMsgKey key) { + m_candidateHits.AppendElement(key); + return NS_OK; +} + +/* void CurrentUrlDone (in nsresult exitCode); */ +NS_IMETHODIMP nsMsgSearchNews::CurrentUrlDone(nsresult exitCode) { + CollateHits(); + ReportHits(); + return NS_OK; +} + +#if 0 // need to switch this to a notify stop loading handler, I think. +void nsMsgSearchNews::PreExitFunction (URL_Struct * /*url*/, int status, MWContext *context) +{ + MSG_SearchFrame *frame = MSG_SearchFrame::FromContext (context); + nsMsgSearchNews *adapter = (nsMsgSearchNews*) frame->GetRunningAdapter(); + adapter->CollateHits(); + adapter->ReportHits(); + + if (status == MK_INTERRUPTED) + { + adapter->Abort(); + frame->EndCylonMode(); + } + else + { + frame->m_idxRunningScope++; + if (frame->m_idxRunningScope >= frame->m_scopeList.Count()) + frame->EndCylonMode(); + } +} +#endif // 0 + +void nsMsgSearchNews::CollateHits() { + // Since the XPAT commands are processed one at a time, the result set for the + // entire query is the intersection of results for each XPAT command if an AND + // search, otherwise we want the union of all the search hits (minus the + // duplicates of course). + + uint32_t size = m_candidateHits.Length(); + if (!size) return; + + // Sort the article numbers first, so it's easy to tell how many hits + // on a given article we got + m_candidateHits.Sort(); + + // For an OR search we only need to count the first occurrence of a candidate. + uint32_t termCount = 1; + MOZ_ASSERT(m_searchType != ST_UNINITIALIZED, + "m_searchType accessed without being set"); + if (m_searchType == ST_AND_SEARCH) { + // We have a traditional AND search which must be collated. In order to + // get promoted into the hits list, a candidate article number must appear + // in the results of each XPAT command. So if we fire 3 XPAT commands (one + // per search term), the article number must appear 3 times. If it appears + // fewer than 3 times, it matched some search terms, but not all. + termCount = m_searchTerms.Length(); + } + uint32_t candidateCount = 0; + uint32_t candidate = m_candidateHits[0]; + for (uint32_t index = 0; index < size; ++index) { + uint32_t possibleCandidate = m_candidateHits[index]; + if (candidate == possibleCandidate) { + ++candidateCount; + } else { + candidateCount = 1; + candidate = possibleCandidate; + } + if (candidateCount == termCount) m_hits.AppendElement(candidate); + } +} + +void nsMsgSearchNews::ReportHits() { + nsCOMPtr<nsIMsgDatabase> db; + nsCOMPtr<nsIDBFolderInfo> folderInfo; + nsCOMPtr<nsIMsgFolder> scopeFolder; + + nsresult err = m_scope->GetFolder(getter_AddRefs(scopeFolder)); + if (NS_SUCCEEDED(err) && scopeFolder) { + err = scopeFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), + getter_AddRefs(db)); + } + + if (db) { + uint32_t size = m_hits.Length(); + for (uint32_t i = 0; i < size; ++i) { + nsCOMPtr<nsIMsgDBHdr> header; + + db->GetMsgHdrForKey(m_hits.ElementAt(i), getter_AddRefs(header)); + if (header) ReportHit(header, scopeFolder); + } + } +} + +// ### this should take an nsIMsgFolder instead of a string location. +void nsMsgSearchNews::ReportHit(nsIMsgDBHdr* pHeaders, nsIMsgFolder* folder) { + // this is totally filched from msg_SearchOfflineMail until I decide whether + // the right thing is to get them from the db or from NNTP + nsCOMPtr<nsIMsgSearchSession> session; + nsCOMPtr<nsIMsgFolder> scopeFolder; + m_scope->GetFolder(getter_AddRefs(scopeFolder)); + m_scope->GetSearchSession(getter_AddRefs(session)); + if (session) session->AddSearchHit(pHeaders, scopeFolder); +} + +nsresult nsMsgSearchValidityManager::InitNewsTable() { + NS_ASSERTION(nullptr == m_newsTable, "don't call this twice!"); + nsresult rv = NewTable(getter_AddRefs(m_newsTable)); + + if (NS_SUCCEEDED(rv)) { + // clang-format off + m_newsTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + m_newsTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + m_newsTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); + m_newsTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); + m_newsTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); + m_newsTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); + m_newsTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); + m_newsTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); + + m_newsTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + m_newsTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + m_newsTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); + m_newsTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); + m_newsTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); + m_newsTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); + m_newsTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); + m_newsTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); + +#if 0 + // Size should be handled after the fact... + m_newsTable->SetAvailable(nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); + m_newsTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); + m_newsTable->SetAvailable(nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); + m_newsTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); +#endif + m_newsTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + m_newsTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + m_newsTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + m_newsTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + m_newsTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + m_newsTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + m_newsTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + m_newsTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + // clang-format on + } + + return rv; +} + +nsresult nsMsgSearchValidityManager::InitNewsFilterTable() { + NS_ASSERTION(nullptr == m_newsFilterTable, + "news filter table already initted"); + nsresult rv = NewTable(getter_AddRefs(m_newsFilterTable)); + + if (NS_SUCCEEDED(rv)) { + // clang-format off + m_newsFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Contains, 1); + m_newsFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::DoesntContain, 1); + m_newsFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Is, 1); + m_newsFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::Isnt, 1); + m_newsFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::BeginsWith, 1); + m_newsFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::EndsWith, 1); + + m_newsFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsInAB, 1); + m_newsFilterTable->SetAvailable(nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Sender, nsMsgSearchOp::IsntInAB, 1); + + m_newsFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Contains, 1); + m_newsFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::DoesntContain, 1); + m_newsFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Is, 1); + m_newsFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::Isnt, 1); + m_newsFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::BeginsWith, 1); + m_newsFilterTable->SetAvailable(nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Subject, nsMsgSearchOp::EndsWith, 1); + + m_newsFilterTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsBefore, 1); + m_newsFilterTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::IsAfter, 1); + m_newsFilterTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Is, 1); + m_newsFilterTable->SetAvailable(nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Date, nsMsgSearchOp::Isnt, 1); + + m_newsFilterTable->SetAvailable(nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsGreaterThan, 1); + m_newsFilterTable->SetAvailable(nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::Size, nsMsgSearchOp::IsLessThan, 1); + + m_newsFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1); + m_newsFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1); + m_newsFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1); + m_newsFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1); + m_newsFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1); + m_newsFilterTable->SetAvailable(nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + m_newsFilterTable->SetEnabled (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1); + // clang-format on + } + + return rv; +} diff --git a/comm/mailnews/search/src/nsMsgSearchNews.h b/comm/mailnews/search/src/nsMsgSearchNews.h new file mode 100644 index 0000000000..38045d978a --- /dev/null +++ b/comm/mailnews/search/src/nsMsgSearchNews.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef _nsMsgSearchNews_h__ +# include "nsMsgSearchAdapter.h" +# include "MailNewsTypes.h" +# include "nsTArray.h" + +typedef enum search_type { + ST_UNINITIALIZED, + ST_OR_SEARCH, + ST_AND_SEARCH +} search_type; + +//----------------------------------------------------------------------------- +//---------- Adapter class for searching online (news) folders ---------------- +//----------------------------------------------------------------------------- + +class nsMsgSearchNews : public nsMsgSearchAdapter { + public: + nsMsgSearchNews(nsMsgSearchScopeTerm* scope, + nsTArray<RefPtr<nsIMsgSearchTerm>> const& termList); + virtual ~nsMsgSearchNews(); + + NS_IMETHOD ValidateTerms() override; + NS_IMETHOD Search(bool* aDone) override; + NS_IMETHOD GetEncoding(char** result) override; + NS_IMETHOD AddHit(nsMsgKey key) override; + NS_IMETHOD CurrentUrlDone(nsresult exitCode) override; + + virtual nsresult Encode(nsCString* outEncoding); + virtual char* EncodeTerm(nsIMsgSearchTerm*); + char16_t* EncodeToWildmat(const char16_t*); + + void ReportHits(); + void CollateHits(); + void ReportHit(nsIMsgDBHdr* pHeaders, nsIMsgFolder* folder); + + protected: + nsCString m_encoding; + search_type m_searchType; + + nsTArray<nsMsgKey> m_candidateHits; + nsTArray<nsMsgKey> m_hits; + + static const char* m_kNntpFrom; + static const char* m_kNntpSubject; + static const char* m_kTermSeparator; + static const char* m_kUrlPrefix; +}; + +#endif diff --git a/comm/mailnews/search/src/nsMsgSearchSession.cpp b/comm/mailnews/search/src/nsMsgSearchSession.cpp new file mode 100644 index 0000000000..51d5d363d2 --- /dev/null +++ b/comm/mailnews/search/src/nsMsgSearchSession.cpp @@ -0,0 +1,576 @@ +/* -*- 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 "nsMsgSearchCore.h" +#include "nsMsgSearchAdapter.h" +#include "nsMsgSearchBoolExpression.h" +#include "nsMsgSearchSession.h" +#include "nsMsgResultElement.h" +#include "nsMsgSearchTerm.h" +#include "nsMsgSearchScopeTerm.h" +#include "nsIMsgMessageService.h" +#include "nsMsgUtils.h" +#include "nsIMsgSearchNotify.h" +#include "nsIMsgMailSession.h" +#include "nsIMsgWindow.h" +#include "nsMsgFolderFlags.h" +#include "nsMsgLocalSearch.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" + +NS_IMPL_ISUPPORTS(nsMsgSearchSession, nsIMsgSearchSession, nsIUrlListener, + nsISupportsWeakReference) + +nsMsgSearchSession::nsMsgSearchSession() { + m_sortAttribute = nsMsgSearchAttrib::Sender; + m_idxRunningScope = 0; + m_handlingError = false; + m_expressionTree = nullptr; + m_searchPaused = false; + m_iListener = -1; +} + +nsMsgSearchSession::~nsMsgSearchSession() { + InterruptSearch(); + delete m_expressionTree; + DestroyScopeList(); + DestroyTermList(); +} + +NS_IMETHODIMP +nsMsgSearchSession::AddSearchTerm(nsMsgSearchAttribValue attrib, + nsMsgSearchOpValue op, + nsIMsgSearchValue* value, bool BooleanANDp, + const char* customString) { + // stupid gcc + nsMsgSearchBooleanOperator boolOp; + if (BooleanANDp) + boolOp = (nsMsgSearchBooleanOperator)nsMsgSearchBooleanOp::BooleanAND; + else + boolOp = (nsMsgSearchBooleanOperator)nsMsgSearchBooleanOp::BooleanOR; + nsMsgSearchTerm* pTerm = + new nsMsgSearchTerm(attrib, op, value, boolOp, customString); + NS_ENSURE_TRUE(pTerm, NS_ERROR_OUT_OF_MEMORY); + + m_termList.AppendElement(pTerm); + // force the expression tree to rebuild whenever we change the terms + delete m_expressionTree; + m_expressionTree = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchSession::AppendTerm(nsIMsgSearchTerm* aTerm) { + NS_ENSURE_ARG_POINTER(aTerm); + delete m_expressionTree; + m_expressionTree = nullptr; + m_termList.AppendElement(aTerm); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchSession::GetSearchTerms(nsTArray<RefPtr<nsIMsgSearchTerm>>& terms) { + terms = m_termList.Clone(); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchSession::SetSearchTerms( + nsTArray<RefPtr<nsIMsgSearchTerm>> const& terms) { + m_termList = terms.Clone(); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchSession::CreateTerm(nsIMsgSearchTerm** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + NS_ADDREF(*aResult = new nsMsgSearchTerm); + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchSession::RegisterListener( + nsIMsgSearchNotify* aListener, int32_t aNotifyFlags) { + NS_ENSURE_ARG_POINTER(aListener); + m_listenerList.AppendElement(aListener); + m_listenerFlagList.AppendElement(aNotifyFlags); + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchSession::UnregisterListener( + nsIMsgSearchNotify* aListener) { + NS_ENSURE_ARG_POINTER(aListener); + size_t listenerIndex = m_listenerList.IndexOf(aListener); + if (listenerIndex != m_listenerList.NoIndex) { + m_listenerList.RemoveElementAt(listenerIndex); + m_listenerFlagList.RemoveElementAt(listenerIndex); + + // Adjust our iterator if it is active. + // Removal of something at a higher index than the iterator does not affect + // it; we only care if the the index we were pointing at gets shifted down, + // in which case we also want to shift down. + if (m_iListener != -1 && (signed)listenerIndex <= m_iListener) + m_iListener--; + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchSession::GetNumSearchTerms(uint32_t* aNumSearchTerms) { + NS_ENSURE_ARG(aNumSearchTerms); + *aNumSearchTerms = m_termList.Length(); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchSession::GetNthSearchTerm(int32_t whichTerm, + nsMsgSearchAttribValue attrib, + nsMsgSearchOpValue op, + nsIMsgSearchValue* value) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgSearchSession::CountSearchScopes(int32_t* _retval) { + NS_ENSURE_ARG_POINTER(_retval); + *_retval = m_scopeList.Length(); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchSession::GetNthSearchScope(int32_t which, + nsMsgSearchScopeValue* scopeId, + nsIMsgFolder** folder) { + NS_ENSURE_ARG_POINTER(scopeId); + NS_ENSURE_ARG_POINTER(folder); + + nsMsgSearchScopeTerm* scopeTerm = m_scopeList.SafeElementAt(which, nullptr); + NS_ENSURE_ARG(scopeTerm); + + *scopeId = scopeTerm->m_attribute; + NS_IF_ADDREF(*folder = scopeTerm->m_folder); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchSession::AddScopeTerm(nsMsgSearchScopeValue scope, + nsIMsgFolder* folder) { + if (scope != nsMsgSearchScope::allSearchableGroups) { + NS_ASSERTION(folder, "need folder if not searching all groups"); + NS_ENSURE_TRUE(folder, NS_ERROR_NULL_POINTER); + } + + nsMsgSearchScopeTerm* pScopeTerm = + new nsMsgSearchScopeTerm(this, scope, folder); + NS_ENSURE_TRUE(pScopeTerm, NS_ERROR_OUT_OF_MEMORY); + + m_scopeList.AppendElement(pScopeTerm); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchSession::AddDirectoryScopeTerm(nsMsgSearchScopeValue scope) { + nsMsgSearchScopeTerm* pScopeTerm = + new nsMsgSearchScopeTerm(this, scope, nullptr); + NS_ENSURE_TRUE(pScopeTerm, NS_ERROR_OUT_OF_MEMORY); + + m_scopeList.AppendElement(pScopeTerm); + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchSession::ClearScopes() { + DestroyScopeList(); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchSession::ScopeUsesCustomHeaders(nsMsgSearchScopeValue scope, + void* selection, bool forFilters, + bool* _retval) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMsgSearchSession::IsStringAttribute(nsMsgSearchAttribValue attrib, + bool* _retval) { + // Is this check needed? + NS_ENSURE_ARG(_retval); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMsgSearchSession::AddAllScopes(nsMsgSearchScopeValue attrib) { + // don't think this is needed. + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgSearchSession::Search(nsIMsgWindow* aWindow) { + nsresult rv = Initialize(); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgSearchNotify> listener; + m_iListener = 0; + while (m_iListener != -1 && m_iListener < (signed)m_listenerList.Length()) { + listener = m_listenerList[m_iListener]; + int32_t listenerFlags = m_listenerFlagList[m_iListener++]; + if (!listenerFlags || (listenerFlags & nsIMsgSearchSession::onNewSearch)) + listener->OnNewSearch(); + } + m_iListener = -1; + + m_msgWindowWeak = do_GetWeakReference(aWindow); + + return BeginSearching(); +} + +NS_IMETHODIMP nsMsgSearchSession::InterruptSearch() { + nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak)); + if (msgWindow) { + EnableFolderNotifications(true); + if (m_idxRunningScope < m_scopeList.Length()) msgWindow->StopUrls(); + + while (m_idxRunningScope < m_scopeList.Length()) { + ReleaseFolderDBRef(); + m_idxRunningScope++; + } + // m_idxRunningScope = m_scopeList.Length() so it will make us not run + // another url + } + if (m_backgroundTimer) { + m_backgroundTimer->Cancel(); + NotifyListenersDone(NS_MSG_SEARCH_INTERRUPTED); + + m_backgroundTimer = nullptr; + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchSession::PauseSearch() { + if (m_backgroundTimer) { + m_backgroundTimer->Cancel(); + m_searchPaused = true; + return NS_OK; + } else + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsMsgSearchSession::ResumeSearch() { + if (m_searchPaused) { + m_searchPaused = false; + return StartTimer(); + } else + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsMsgSearchSession::GetNumResults(int32_t* aNumResults) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgSearchSession::SetWindow(nsIMsgWindow* aWindow) { + m_msgWindowWeak = do_GetWeakReference(aWindow); + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchSession::GetWindow(nsIMsgWindow** aWindow) { + NS_ENSURE_ARG_POINTER(aWindow); + *aWindow = nullptr; + nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak)); + msgWindow.forget(aWindow); + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchSession::OnStartRunningUrl(nsIURI* url) { + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchSession::OnStopRunningUrl(nsIURI* url, + nsresult aExitCode) { + nsCOMPtr<nsIMsgSearchAdapter> runningAdapter; + + nsresult rv = GetRunningAdapter(getter_AddRefs(runningAdapter)); + // tell the current adapter that the current url has run. + if (NS_SUCCEEDED(rv) && runningAdapter) { + runningAdapter->CurrentUrlDone(aExitCode); + EnableFolderNotifications(true); + ReleaseFolderDBRef(); + } + if (++m_idxRunningScope < m_scopeList.Length()) + DoNextSearch(); + else + NotifyListenersDone(aExitCode); + return NS_OK; +} + +nsresult nsMsgSearchSession::Initialize() { + // Loop over scope terms, initializing an adapter per term. This + // architecture is necessitated by two things: + // 1. There might be more than one kind of adapter per if online + // *and* offline mail mail folders are selected, or if newsgroups + // belonging to Dredd *and* INN are selected + // 2. Most of the protocols are only capable of searching one scope at a + // time, so we'll do each scope in a separate adapter on the client + + nsMsgSearchScopeTerm* scopeTerm = nullptr; + nsresult rv = NS_OK; + + uint32_t numTerms = m_termList.Length(); + // Ensure that the FE has added scopes and terms to this search + NS_ASSERTION(numTerms > 0, "no terms to search!"); + if (numTerms == 0) return NS_MSG_ERROR_NO_SEARCH_VALUES; + + // if we don't have any search scopes to search, return that code. + if (m_scopeList.Length() == 0) return NS_MSG_ERROR_INVALID_SEARCH_SCOPE; + + m_runningUrl.Truncate(); // clear out old url, if any. + m_idxRunningScope = 0; + + // If this term list (loosely specified here by the first term) should be + // scheduled in parallel, build up a list of scopes to do the round-robin + // scheduling + for (uint32_t i = 0; i < m_scopeList.Length() && NS_SUCCEEDED(rv); i++) { + scopeTerm = m_scopeList.ElementAt(i); + // NS_ASSERTION(scopeTerm->IsValid()); + + rv = scopeTerm->InitializeAdapter(m_termList); + } + + return rv; +} + +nsresult nsMsgSearchSession::BeginSearching() { + // Here's a sloppy way to start the URL, but I don't really have time to + // unify the scheduling mechanisms. If the first scope is a newsgroup, and + // it's not Dredd-capable, we build the URL queue. All other searches can be + // done with one URL + nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak)); + if (msgWindow) msgWindow->SetStopped(false); + return DoNextSearch(); +} + +nsresult nsMsgSearchSession::DoNextSearch() { + nsMsgSearchScopeTerm* scope = m_scopeList.ElementAt(m_idxRunningScope); + if (scope->m_attribute == nsMsgSearchScope::onlineMail || + (scope->m_attribute == nsMsgSearchScope::news && scope->m_searchServer)) { + if (scope->m_adapter) { + m_runningUrl.Truncate(); + scope->m_adapter->GetEncoding(getter_Copies(m_runningUrl)); + } + NS_ENSURE_STATE(!m_runningUrl.IsEmpty()); + return GetNextUrl(); + } else { + return SearchWOUrls(); + } +} + +nsresult nsMsgSearchSession::GetNextUrl() { + nsCOMPtr<nsIMsgMessageService> msgService; + + bool stopped = false; + nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak)); + if (msgWindow) msgWindow->GetStopped(&stopped); + if (stopped) return NS_OK; + + nsMsgSearchScopeTerm* currentTerm = GetRunningScope(); + NS_ENSURE_TRUE(currentTerm, NS_ERROR_NULL_POINTER); + EnableFolderNotifications(false); + nsCOMPtr<nsIMsgFolder> folder = currentTerm->m_folder; + if (folder) { + nsCString folderUri; + folder->GetURI(folderUri); + nsresult rv = + GetMessageServiceFromURI(folderUri, getter_AddRefs(msgService)); + + if (NS_SUCCEEDED(rv) && msgService && currentTerm) + msgService->Search(this, msgWindow, currentTerm->m_folder, m_runningUrl); + return rv; + } + return NS_OK; +} + +/* static */ +void nsMsgSearchSession::TimerCallback(nsITimer* aTimer, void* aClosure) { + NS_ENSURE_TRUE_VOID(aClosure); + nsMsgSearchSession* searchSession = (nsMsgSearchSession*)aClosure; + bool done; + bool stopped = false; + + searchSession->TimeSlice(&done); + nsCOMPtr<nsIMsgWindow> msgWindow( + do_QueryReferent(searchSession->m_msgWindowWeak)); + if (msgWindow) msgWindow->GetStopped(&stopped); + + if (done || stopped) { + if (aTimer) aTimer->Cancel(); + searchSession->m_backgroundTimer = nullptr; + if (searchSession->m_idxRunningScope < searchSession->m_scopeList.Length()) + searchSession->DoNextSearch(); + else + searchSession->NotifyListenersDone(NS_OK); + } +} + +nsresult nsMsgSearchSession::StartTimer() { + nsresult rv; + + m_backgroundTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + m_backgroundTimer->InitWithNamedFuncCallback( + TimerCallback, (void*)this, 0, nsITimer::TYPE_REPEATING_SLACK, + "nsMsgSearchSession::TimerCallback"); + TimerCallback(m_backgroundTimer, this); + return NS_OK; +} + +nsresult nsMsgSearchSession::SearchWOUrls() { + EnableFolderNotifications(false); + return StartTimer(); +} + +NS_IMETHODIMP +nsMsgSearchSession::GetRunningAdapter(nsIMsgSearchAdapter** aSearchAdapter) { + NS_ENSURE_ARG_POINTER(aSearchAdapter); + *aSearchAdapter = nullptr; + nsMsgSearchScopeTerm* scope = GetRunningScope(); + if (scope) { + NS_IF_ADDREF(*aSearchAdapter = scope->m_adapter); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchSession::AddSearchHit(nsIMsgDBHdr* aHeader, + nsIMsgFolder* aFolder) { + nsCOMPtr<nsIMsgSearchNotify> listener; + m_iListener = 0; + while (m_iListener != -1 && m_iListener < (signed)m_listenerList.Length()) { + listener = m_listenerList[m_iListener]; + int32_t listenerFlags = m_listenerFlagList[m_iListener++]; + if (!listenerFlags || (listenerFlags & nsIMsgSearchSession::onSearchHit)) + listener->OnSearchHit(aHeader, aFolder); + } + m_iListener = -1; + return NS_OK; +} + +nsresult nsMsgSearchSession::NotifyListenersDone(nsresult aStatus) { + // need to stabilize "this" in case one of the listeners releases the last + // reference to us. + RefPtr<nsIMsgSearchSession> kungFuDeathGrip(this); + + nsCOMPtr<nsIMsgSearchNotify> listener; + m_iListener = 0; + while (m_iListener != -1 && m_iListener < (signed)m_listenerList.Length()) { + listener = m_listenerList[m_iListener]; + int32_t listenerFlags = m_listenerFlagList[m_iListener++]; + if (!listenerFlags || (listenerFlags & nsIMsgSearchSession::onSearchDone)) + listener->OnSearchDone(aStatus); + } + m_iListener = -1; + return NS_OK; +} + +void nsMsgSearchSession::DestroyScopeList() { + nsMsgSearchScopeTerm* scope = nullptr; + + for (int32_t i = m_scopeList.Length() - 1; i >= 0; i--) { + scope = m_scopeList.ElementAt(i); + // NS_ASSERTION (scope->IsValid(), "invalid search scope"); + if (scope->m_adapter) scope->m_adapter->ClearScope(); + } + m_scopeList.Clear(); +} + +void nsMsgSearchSession::DestroyTermList() { m_termList.Clear(); } + +nsMsgSearchScopeTerm* nsMsgSearchSession::GetRunningScope() { + return m_scopeList.SafeElementAt(m_idxRunningScope, nullptr); +} + +nsresult nsMsgSearchSession::TimeSlice(bool* aDone) { + // we only do serial for now. + return TimeSliceSerial(aDone); +} + +void nsMsgSearchSession::ReleaseFolderDBRef() { + nsMsgSearchScopeTerm* scope = GetRunningScope(); + if (!scope) return; + + bool isOpen = false; + uint32_t flags; + nsCOMPtr<nsIMsgFolder> folder; + scope->GetFolder(getter_AddRefs(folder)); + nsCOMPtr<nsIMsgMailSession> mailSession = + do_GetService("@mozilla.org/messenger/services/session;1"); + if (!mailSession || !folder) return; + + mailSession->IsFolderOpenInWindow(folder, &isOpen); + folder->GetFlags(&flags); + + /*we don't null out the db reference for inbox because inbox is like the + "main" folder and performance outweighs footprint */ + if (!isOpen && !(nsMsgFolderFlags::Inbox & flags)) + folder->SetMsgDatabase(nullptr); +} +nsresult nsMsgSearchSession::TimeSliceSerial(bool* aDone) { + // This version of TimeSlice runs each scope term one at a time, and waits + // until one scope term is finished before starting another one. When we're + // searching the local disk, this is the fastest way to do it. + + NS_ENSURE_ARG_POINTER(aDone); + + nsMsgSearchScopeTerm* scope = GetRunningScope(); + if (!scope) { + *aDone = true; + return NS_OK; + } + + nsresult rv = scope->TimeSlice(aDone); + if (*aDone || NS_FAILED(rv)) { + EnableFolderNotifications(true); + ReleaseFolderDBRef(); + m_idxRunningScope++; + EnableFolderNotifications(false); + // check if the next scope is an online search; if so, + // set *aDone to true so that we'll try to run the next + // search in TimerCallback. + scope = GetRunningScope(); + if (scope && (scope->m_attribute == nsMsgSearchScope::onlineMail || + (scope->m_attribute == nsMsgSearchScope::news && + scope->m_searchServer))) { + *aDone = true; + return rv; + } + } + *aDone = false; + return rv; +} + +void nsMsgSearchSession::EnableFolderNotifications(bool aEnable) { + nsMsgSearchScopeTerm* scope = GetRunningScope(); + if (scope) { + nsCOMPtr<nsIMsgFolder> folder; + scope->GetFolder(getter_AddRefs(folder)); + if (folder) // enable msg count notifications + folder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications, + aEnable); + } +} + +// this method is used for adding new hdrs to quick search view +NS_IMETHODIMP +nsMsgSearchSession::MatchHdr(nsIMsgDBHdr* aMsgHdr, nsIMsgDatabase* aDatabase, + bool* aResult) { + nsMsgSearchScopeTerm* scope = m_scopeList.SafeElementAt(0, nullptr); + if (scope) { + if (!scope->m_adapter) scope->InitializeAdapter(m_termList); + if (scope->m_adapter) { + nsAutoString nullCharset, folderCharset; + scope->m_adapter->GetSearchCharsets(nullCharset, folderCharset); + NS_ConvertUTF16toUTF8 charset(folderCharset.get()); + nsMsgSearchOfflineMail::MatchTermsForSearch( + aMsgHdr, m_termList, charset.get(), scope, aDatabase, + &m_expressionTree, aResult); + } + } + return NS_OK; +} diff --git a/comm/mailnews/search/src/nsMsgSearchSession.h b/comm/mailnews/search/src/nsMsgSearchSession.h new file mode 100644 index 0000000000..a5f311e1e5 --- /dev/null +++ b/comm/mailnews/search/src/nsMsgSearchSession.h @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +#ifndef nsMsgSearchSession_h___ +#define nsMsgSearchSession_h___ + +#include "nscore.h" +#include "nsMsgSearchCore.h" +#include "nsIMsgSearchSession.h" +#include "nsIUrlListener.h" +#include "nsITimer.h" +#include "nsWeakReference.h" + +class nsMsgSearchAdapter; +class nsMsgSearchBoolExpression; +class nsMsgSearchScopeTerm; + +class nsMsgSearchSession : public nsIMsgSearchSession, + public nsIUrlListener, + public nsSupportsWeakReference { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGSEARCHSESSION + NS_DECL_NSIURLLISTENER + + nsMsgSearchSession(); + + protected: + virtual ~nsMsgSearchSession(); + + nsWeakPtr m_msgWindowWeak; + nsresult Initialize(); + nsresult StartTimer(); + nsresult TimeSlice(bool* aDone); + nsMsgSearchScopeTerm* GetRunningScope(); + void StopRunning(); + nsresult BeginSearching(); + nsresult DoNextSearch(); + nsresult SearchWOUrls(); + nsresult GetNextUrl(); + nsresult NotifyListenersDone(nsresult status); + void EnableFolderNotifications(bool aEnable); + void ReleaseFolderDBRef(); + + nsTArray<RefPtr<nsMsgSearchScopeTerm>> m_scopeList; + nsTArray<RefPtr<nsIMsgSearchTerm>> m_termList; + + nsTArray<nsCOMPtr<nsIMsgSearchNotify>> m_listenerList; + nsTArray<int32_t> m_listenerFlagList; + /** + * Iterator index for m_listenerList/m_listenerFlagList. We used to use an + * nsTObserverArray for m_listenerList but its auto-adjusting iterator was + * not helping us keep our m_listenerFlagList iterator correct. + * + * We are making the simplifying assumption that our notifications are + * non-reentrant. In the exceptional case that it turns out they are + * reentrant, we assume that this is the result of canceling a search while + * the session is active and initiating a new one. In that case, we assume + * the outer iteration can safely be abandoned. + * + * This value is defined to be the index of the next listener we will process. + * This allows us to use the sentinel value of -1 to convey that no iteration + * is in progress (and the iteration process to abort if the value transitions + * to -1, which we always set on conclusion of our loop). + */ + int32_t m_iListener; + + void DestroyTermList(); + void DestroyScopeList(); + + static void TimerCallback(nsITimer* aTimer, void* aClosure); + // support for searching multiple scopes in serial + nsresult TimeSliceSerial(bool* aDone); + nsresult TimeSliceParallel(); + + nsMsgSearchAttribValue m_sortAttribute; + uint32_t m_idxRunningScope; + bool m_handlingError; + nsCString m_runningUrl; // The url for the current search + nsCOMPtr<nsITimer> m_backgroundTimer; + bool m_searchPaused; + nsMsgSearchBoolExpression* m_expressionTree; +}; + +#endif diff --git a/comm/mailnews/search/src/nsMsgSearchTerm.cpp b/comm/mailnews/search/src/nsMsgSearchTerm.cpp new file mode 100644 index 0000000000..7aad1628e5 --- /dev/null +++ b/comm/mailnews/search/src/nsMsgSearchTerm.cpp @@ -0,0 +1,1797 @@ +/* -*- 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 "prmem.h" +#include "nsMsgSearchCore.h" +#include "nsIMsgSearchSession.h" +#include "nsMsgUtils.h" +#include "nsIMsgDatabase.h" +#include "nsIMsgHdr.h" +#include "nsMsgSearchTerm.h" +#include "nsMsgSearchScopeTerm.h" +#include "nsMsgBodyHandler.h" +#include "nsMsgResultElement.h" +#include "nsIMsgImapMailFolder.h" +#include "nsMsgSearchImap.h" +#include "nsMsgLocalSearch.h" +#include "nsMsgSearchNews.h" +#include "nsMsgSearchValue.h" +#include "nsMsgI18N.h" +#include "nsIMimeConverter.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsIMsgFilterPlugin.h" +#include "nsIFile.h" +#include "nsISeekableStream.h" +#include "nsNetCID.h" +#include "nsIFileStreams.h" +#include "nsUnicharUtils.h" +#include "nsIAbCard.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include <ctype.h> +#include "nsIMsgTagService.h" +#include "nsMsgMessageFlags.h" +#include "nsIMsgFilterService.h" +#include "nsIMsgPluggableStore.h" +#include "nsIAbManager.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/mailnews/MimeHeaderParser.h" +#include "mozilla/Utf8.h" + +using namespace mozilla::mailnews; + +//--------------------------------------------------------------------------- +// nsMsgSearchTerm specifies one criterion, e.g. name contains phil +//--------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +//-------------------- Implementation of nsMsgSearchTerm ----------------------- +//----------------------------------------------------------------------------- +#define MAILNEWS_CUSTOM_HEADERS "mailnews.customHeaders" + +typedef struct { + nsMsgSearchAttribValue attrib; + const char* attribName; +} nsMsgSearchAttribEntry; + +nsMsgSearchAttribEntry SearchAttribEntryTable[] = { + {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, "to or cc"}, + {nsMsgSearchAttrib::AllAddresses, "all addresses"}, + {nsMsgSearchAttrib::AgeInDays, "age in days"}, + {nsMsgSearchAttrib::Keywords, "tag"}, + {nsMsgSearchAttrib::Size, "size"}, + // this used to be nsMsgSearchAttrib::SenderInAddressBook + // we used to have two Sender menuitems + // for backward compatibility, we can still parse + // the old style. see bug #179803 + {nsMsgSearchAttrib::Sender, "from in ab"}, + {nsMsgSearchAttrib::JunkStatus, "junk status"}, + {nsMsgSearchAttrib::JunkPercent, "junk percent"}, + {nsMsgSearchAttrib::JunkScoreOrigin, "junk score origin"}, + {nsMsgSearchAttrib::HasAttachmentStatus, "has attachment status"}, +}; + +static const unsigned int sNumSearchAttribEntryTable = + MOZ_ARRAY_LENGTH(SearchAttribEntryTable); + +// Take a string which starts off with an attribute +// and return the matching attribute. If the string is not in the table, and it +// begins with a quote, then we can conclude that it is an arbitrary header. +// Otherwise if not in the table, it is the id for a custom search term. +nsresult NS_MsgGetAttributeFromString(const char* string, + nsMsgSearchAttribValue* attrib, + nsACString& aCustomId) { + NS_ENSURE_ARG_POINTER(string); + NS_ENSURE_ARG_POINTER(attrib); + + bool found = false; + bool isArbitraryHeader = false; + // arbitrary headers have a leading quote + if (*string != '"') { + for (unsigned int idxAttrib = 0; idxAttrib < sNumSearchAttribEntryTable; + idxAttrib++) { + if (!PL_strcasecmp(string, + SearchAttribEntryTable[idxAttrib].attribName)) { + found = true; + *attrib = SearchAttribEntryTable[idxAttrib].attrib; + break; + } + } + } else // remove the leading quote + { + string++; + isArbitraryHeader = true; + } + + if (!found && !isArbitraryHeader) { + // must be a custom attribute + *attrib = nsMsgSearchAttrib::Custom; + aCustomId.Assign(string); + return NS_OK; + } + + if (!found) { + nsresult rv; + bool goodHdr; + IsRFC822HeaderFieldName(string, &goodHdr); + if (!goodHdr) return NS_MSG_INVALID_CUSTOM_HEADER; + // 49 is for showing customize... in ui, headers start from 50 onwards up + // until 99. + *attrib = nsMsgSearchAttrib::OtherHeader + 1; + + nsCOMPtr<nsIPrefService> prefService = + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrefBranch> prefBranch; + rv = prefService->GetBranch(nullptr, getter_AddRefs(prefBranch)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString headers; + prefBranch->GetCharPref(MAILNEWS_CUSTOM_HEADERS, headers); + + if (!headers.IsEmpty()) { + nsAutoCString hdrStr(headers); + hdrStr.StripWhitespace(); // remove whitespace before parsing + + char* newStr = hdrStr.BeginWriting(); + char* token = NS_strtok(":", &newStr); + uint32_t i = 0; + while (token) { + if (PL_strcasecmp(token, string) == 0) { + *attrib += i; // we found custom header in the pref + found = true; + break; + } + token = NS_strtok(":", &newStr); + i++; + } + } + } + // If we didn't find the header in MAILNEWS_CUSTOM_HEADERS, we're + // going to return NS_OK and an attrib of nsMsgSearchAttrib::OtherHeader+1. + // in case it's a client side spam filter description filter, + // which doesn't add its headers to mailnews.customMailHeaders. + // We've already checked that it's a valid header and returned + // an error if so. + + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchTerm::GetAttributeFromString( + const char* aString, nsMsgSearchAttribValue* aAttrib) { + nsAutoCString customId; + return NS_MsgGetAttributeFromString(aString, aAttrib, customId); +} + +nsresult NS_MsgGetStringForAttribute(int16_t attrib, const char** string) { + NS_ENSURE_ARG_POINTER(string); + + bool found = false; + for (unsigned int idxAttrib = 0; idxAttrib < sNumSearchAttribEntryTable; + idxAttrib++) { + // I'm using the idx's as aliases into MSG_SearchAttribute and + // MSG_SearchOperator enums which is legal because of the way the + // enums are defined (starts at 0, numItems at end) + if (attrib == SearchAttribEntryTable[idxAttrib].attrib) { + found = true; + *string = SearchAttribEntryTable[idxAttrib].attribName; + break; + } + } + if (!found) *string = ""; // don't leave the string uninitialized + + // we no longer return invalid attribute. If we cannot find the string in the + // table, then it is an arbitrary header. Return success regardless if found + // or not + return NS_OK; +} + +typedef struct { + nsMsgSearchOpValue op; + const char* opName; +} nsMsgSearchOperatorEntry; + +nsMsgSearchOperatorEntry SearchOperatorEntryTable[] = { + {nsMsgSearchOp::Contains, "contains"}, + {nsMsgSearchOp::DoesntContain, "doesn't contain"}, + {nsMsgSearchOp::Is, "is"}, + {nsMsgSearchOp::Isnt, "isn't"}, + {nsMsgSearchOp::IsEmpty, "is empty"}, + {nsMsgSearchOp::IsntEmpty, "isn't empty"}, + {nsMsgSearchOp::IsBefore, "is before"}, + {nsMsgSearchOp::IsAfter, "is after"}, + {nsMsgSearchOp::IsHigherThan, "is higher than"}, + {nsMsgSearchOp::IsLowerThan, "is lower than"}, + {nsMsgSearchOp::BeginsWith, "begins with"}, + {nsMsgSearchOp::EndsWith, "ends with"}, + {nsMsgSearchOp::IsInAB, "is in ab"}, + {nsMsgSearchOp::IsntInAB, "isn't in ab"}, + {nsMsgSearchOp::IsGreaterThan, "is greater than"}, + {nsMsgSearchOp::IsLessThan, "is less than"}, + {nsMsgSearchOp::Matches, "matches"}, + {nsMsgSearchOp::DoesntMatch, "doesn't match"}}; + +static const unsigned int sNumSearchOperatorEntryTable = + MOZ_ARRAY_LENGTH(SearchOperatorEntryTable); + +nsresult NS_MsgGetOperatorFromString(const char* string, int16_t* op) { + NS_ENSURE_ARG_POINTER(string); + NS_ENSURE_ARG_POINTER(op); + + bool found = false; + for (unsigned int idxOp = 0; idxOp < sNumSearchOperatorEntryTable; idxOp++) { + // I'm using the idx's as aliases into MSG_SearchAttribute and + // MSG_SearchOperator enums which is legal because of the way the + // enums are defined (starts at 0, numItems at end) + if (!PL_strcasecmp(string, SearchOperatorEntryTable[idxOp].opName)) { + found = true; + *op = SearchOperatorEntryTable[idxOp].op; + break; + } + } + return (found) ? NS_OK : NS_ERROR_INVALID_ARG; +} + +nsresult NS_MsgGetStringForOperator(int16_t op, const char** string) { + NS_ENSURE_ARG_POINTER(string); + + bool found = false; + for (unsigned int idxOp = 0; idxOp < sNumSearchOperatorEntryTable; idxOp++) { + // I'm using the idx's as aliases into MSG_SearchAttribute and + // MSG_SearchOperator enums which is legal because of the way the + // enums are defined (starts at 0, numItems at end) + if (op == SearchOperatorEntryTable[idxOp].op) { + found = true; + *string = SearchOperatorEntryTable[idxOp].opName; + break; + } + } + + return (found) ? NS_OK : NS_ERROR_INVALID_ARG; +} + +void NS_MsgGetUntranslatedStatusName(uint32_t s, nsCString* outName) { + const char* tmpOutName = NULL; +#define MSG_STATUS_MASK \ + (nsMsgMessageFlags::Read | nsMsgMessageFlags::Replied | \ + nsMsgMessageFlags::Forwarded | nsMsgMessageFlags::New | \ + nsMsgMessageFlags::Marked) + uint32_t maskOut = (s & MSG_STATUS_MASK); + + // diddle the flags to pay attention to the most important ones first, if + // multiple flags are set. Should remove this code from the winfe. + if (maskOut & nsMsgMessageFlags::New) maskOut = nsMsgMessageFlags::New; + if (maskOut & nsMsgMessageFlags::Replied && + maskOut & nsMsgMessageFlags::Forwarded) + maskOut = nsMsgMessageFlags::Replied | nsMsgMessageFlags::Forwarded; + else if (maskOut & nsMsgMessageFlags::Forwarded) + maskOut = nsMsgMessageFlags::Forwarded; + else if (maskOut & nsMsgMessageFlags::Replied) + maskOut = nsMsgMessageFlags::Replied; + + switch (maskOut) { + case nsMsgMessageFlags::Read: + tmpOutName = "read"; + break; + case nsMsgMessageFlags::Replied: + tmpOutName = "replied"; + break; + case nsMsgMessageFlags::Forwarded: + tmpOutName = "forwarded"; + break; + case nsMsgMessageFlags::Forwarded | nsMsgMessageFlags::Replied: + tmpOutName = "replied and forwarded"; + break; + case nsMsgMessageFlags::New: + tmpOutName = "new"; + break; + case nsMsgMessageFlags::Marked: + tmpOutName = "flagged"; + break; + default: + // This is fine, status may be "unread" for example + break; + } + + if (tmpOutName) *outName = tmpOutName; +} + +int32_t NS_MsgGetStatusValueFromName(char* name) { + if (!strcmp("read", name)) return nsMsgMessageFlags::Read; + if (!strcmp("replied", name)) return nsMsgMessageFlags::Replied; + if (!strcmp("forwarded", name)) return nsMsgMessageFlags::Forwarded; + if (!strcmp("replied and forwarded", name)) + return nsMsgMessageFlags::Forwarded | nsMsgMessageFlags::Replied; + if (!strcmp("new", name)) return nsMsgMessageFlags::New; + if (!strcmp("flagged", name)) return nsMsgMessageFlags::Marked; + return 0; +} + +// Needed for DeStream method. +nsMsgSearchTerm::nsMsgSearchTerm() { + // initialize this to zero + m_value.attribute = 0; + m_value.u.priority = 0; + m_attribute = nsMsgSearchAttrib::Default; + m_operator = nsMsgSearchOp::Contains; + mBeginsGrouping = false; + mEndsGrouping = false; + m_matchAll = false; + + // valgrind warning during GC/java data check suggests + // m_booleanp needs to be initialized too. + m_booleanOp = nsMsgSearchBooleanOp::BooleanAND; +} + +nsMsgSearchTerm::nsMsgSearchTerm(nsMsgSearchAttribValue attrib, + nsMsgSearchOpValue op, nsIMsgSearchValue* val, + nsMsgSearchBooleanOperator boolOp, + const char* aCustomString) { + m_operator = op; + m_attribute = attrib; + m_booleanOp = boolOp; + if (attrib > nsMsgSearchAttrib::OtherHeader && + attrib < nsMsgSearchAttrib::kNumMsgSearchAttributes && aCustomString) { + m_arbitraryHeader = aCustomString; + ToLowerCaseExceptSpecials(m_arbitraryHeader); + } else if (attrib == nsMsgSearchAttrib::Custom) { + m_customId = aCustomString; + } + + nsMsgResultElement::AssignValues(val, &m_value); + m_matchAll = false; + mBeginsGrouping = false; + mEndsGrouping = false; +} + +nsMsgSearchTerm::~nsMsgSearchTerm() {} + +NS_IMPL_ISUPPORTS(nsMsgSearchTerm, nsIMsgSearchTerm) + +// Perhaps we could find a better place for this? +// Caller needs to free. +/* static */ char* nsMsgSearchTerm::EscapeQuotesInStr(const char* str) { + int numQuotes = 0; + for (const char* strPtr = str; *strPtr; strPtr++) + if (*strPtr == '"') numQuotes++; + int escapedStrLen = PL_strlen(str) + numQuotes; + char* escapedStr = (char*)PR_Malloc(escapedStrLen + 1); + if (escapedStr) { + char* destPtr; + for (destPtr = escapedStr; *str; str++) { + if (*str == '"') *destPtr++ = '\\'; + *destPtr++ = *str; + } + *destPtr = '\0'; + } + return escapedStr; +} + +nsresult nsMsgSearchTerm::OutputValue(nsCString& outputStr) { + if (IS_STRING_ATTRIBUTE(m_attribute) && !m_value.utf8String.IsEmpty()) { + bool quoteVal = false; + // need to quote strings with ')' and strings starting with '"' or ' ' + // filter code will escape quotes + if (m_value.utf8String.FindChar(')') != kNotFound || + (m_value.utf8String.First() == ' ') || + (m_value.utf8String.First() == '"')) { + quoteVal = true; + outputStr += "\""; + } + if (m_value.utf8String.FindChar('"') != kNotFound) { + char* escapedString = + nsMsgSearchTerm::EscapeQuotesInStr(m_value.utf8String.get()); + if (escapedString) { + outputStr += escapedString; + PR_Free(escapedString); + } + + } else { + outputStr += m_value.utf8String; + } + if (quoteVal) outputStr += "\""; + } else { + switch (m_attribute) { + case nsMsgSearchAttrib::Date: { + PRExplodedTime exploded; + PR_ExplodeTime(m_value.u.date, PR_LocalTimeParameters, &exploded); + + // wow, so tm_mon is 0 based, tm_mday is 1 based. + char dateBuf[100]; + PR_FormatTimeUSEnglish(dateBuf, sizeof(dateBuf), "%d-%b-%Y", &exploded); + outputStr += dateBuf; + break; + } + case nsMsgSearchAttrib::AgeInDays: { + outputStr.AppendInt(m_value.u.age); + break; + } + case nsMsgSearchAttrib::JunkStatus: { + outputStr.AppendInt( + m_value.u.junkStatus); // only if we write to disk, right? + break; + } + case nsMsgSearchAttrib::JunkPercent: { + outputStr.AppendInt(m_value.u.junkPercent); + break; + } + case nsMsgSearchAttrib::MsgStatus: { + nsAutoCString status; + NS_MsgGetUntranslatedStatusName(m_value.u.msgStatus, &status); + outputStr += status; + break; + } + case nsMsgSearchAttrib::Priority: { + nsAutoCString priority; + NS_MsgGetUntranslatedPriorityName(m_value.u.priority, priority); + outputStr += priority; + break; + } + case nsMsgSearchAttrib::HasAttachmentStatus: { + outputStr.AppendLiteral("true"); // don't need anything here, really + break; + } + case nsMsgSearchAttrib::Size: { + outputStr.AppendInt(m_value.u.size); + break; + } + case nsMsgSearchAttrib::Uint32HdrProperty: { + outputStr.AppendInt(m_value.u.msgStatus); + break; + } + default: + NS_ASSERTION(false, "trying to output invalid attribute"); + break; + } + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchTerm::GetTermAsString(nsACString& outStream) { + const char* operatorStr; + nsAutoCString outputStr; + nsresult rv; + + if (m_matchAll) { + outStream = "ALL"; + return NS_OK; + } + + // if arbitrary header, use it instead! + if (m_attribute > nsMsgSearchAttrib::OtherHeader && + m_attribute < nsMsgSearchAttrib::kNumMsgSearchAttributes) { + outputStr = "\""; + outputStr += m_arbitraryHeader; + outputStr += "\""; + } else if (m_attribute == nsMsgSearchAttrib::Custom) { + // use the custom id as the string + outputStr = m_customId; + } + + else if (m_attribute == nsMsgSearchAttrib::Uint32HdrProperty) { + outputStr = m_hdrProperty; + } else { + const char* attrib; + rv = NS_MsgGetStringForAttribute(m_attribute, &attrib); + NS_ENSURE_SUCCESS(rv, rv); + + outputStr = attrib; + } + + outputStr += ','; + + rv = NS_MsgGetStringForOperator(m_operator, &operatorStr); + NS_ENSURE_SUCCESS(rv, rv); + + outputStr += operatorStr; + outputStr += ','; + + OutputValue(outputStr); + outStream = outputStr; + return NS_OK; +} + +// fill in m_value from the input stream. +nsresult nsMsgSearchTerm::ParseValue(char* inStream) { + if (IS_STRING_ATTRIBUTE(m_attribute)) { + bool quoteVal = false; + while (isspace(*inStream)) inStream++; + // need to remove pair of '"', if present + if (*inStream == '"') { + quoteVal = true; + inStream++; + } + int valueLen = PL_strlen(inStream); + if (quoteVal && inStream[valueLen - 1] == '"') valueLen--; + + m_value.utf8String.Assign(inStream, valueLen); + CopyUTF8toUTF16(m_value.utf8String, m_value.utf16String); + } else { + switch (m_attribute) { + case nsMsgSearchAttrib::Date: + PR_ParseTimeString(inStream, false, &m_value.u.date); + break; + case nsMsgSearchAttrib::MsgStatus: + m_value.u.msgStatus = NS_MsgGetStatusValueFromName(inStream); + break; + case nsMsgSearchAttrib::Priority: + NS_MsgGetPriorityFromString(inStream, m_value.u.priority); + break; + case nsMsgSearchAttrib::AgeInDays: + m_value.u.age = atoi(inStream); + break; + case nsMsgSearchAttrib::JunkStatus: + m_value.u.junkStatus = + atoi(inStream); // only if we read from disk, right? + break; + case nsMsgSearchAttrib::JunkPercent: + m_value.u.junkPercent = atoi(inStream); + break; + case nsMsgSearchAttrib::HasAttachmentStatus: + m_value.u.msgStatus = nsMsgMessageFlags::Attachment; + break; // this should always be true. + case nsMsgSearchAttrib::Size: + m_value.u.size = atoi(inStream); + break; + default: + NS_ASSERTION(false, "invalid attribute parsing search term value"); + break; + } + } + m_value.attribute = m_attribute; + return NS_OK; +} + +// find the operator code for this operator string. +nsresult nsMsgSearchTerm::ParseOperator(char* inStream, + nsMsgSearchOpValue* value) { + NS_ENSURE_ARG_POINTER(value); + int16_t operatorVal; + while (isspace(*inStream)) inStream++; + + char* commaSep = PL_strchr(inStream, ','); + + if (commaSep) *commaSep = '\0'; + + nsresult rv = NS_MsgGetOperatorFromString(inStream, &operatorVal); + if (NS_SUCCEEDED(rv)) *value = (nsMsgSearchOpValue)operatorVal; + return rv; +} + +// find the attribute code for this comma-delimited attribute. +nsresult nsMsgSearchTerm::ParseAttribute(char* inStream, + nsMsgSearchAttribValue* attrib) { + while (isspace(*inStream)) inStream++; + + // if we are dealing with an arbitrary header, it will be quoted.... + // it seems like a kludge, but to distinguish arbitrary headers from + // standard headers with the same name, like "Date", we'll use the + // presence of the quote to recognize arbitrary headers. We leave the + // leading quote as a flag, but remove the trailing quote. + bool quoteVal = false; + if (*inStream == '"') quoteVal = true; + + // arbitrary headers are quoted. Skip first character, which will be the + // first quote for arbitrary headers + char* separator = strchr(inStream + 1, quoteVal ? '"' : ','); + + if (separator) *separator = '\0'; + + nsAutoCString customId; + nsresult rv = NS_MsgGetAttributeFromString(inStream, attrib, m_customId); + NS_ENSURE_SUCCESS(rv, rv); + + if (*attrib > nsMsgSearchAttrib::OtherHeader && + *attrib < nsMsgSearchAttrib::kNumMsgSearchAttributes) { + // We are dealing with an arbitrary header. + m_arbitraryHeader = inStream + 1; // remove the leading quote + ToLowerCaseExceptSpecials(m_arbitraryHeader); + } + return rv; +} + +// De stream one search term. If the condition looks like +// condition = "(to or cc, contains, r-thompson) AND (body, doesn't contain, +// fred)" This routine should get called twice, the first time with "to or cc, +// contains, r-thompson", the second time with "body, doesn't contain, fred" + +nsresult nsMsgSearchTerm::DeStreamNew(char* inStream, int16_t /*length*/) { + if (!strcmp(inStream, "ALL")) { + m_matchAll = true; + return NS_OK; + } + char* commaSep = PL_strchr(inStream, ','); + nsresult rv = ParseAttribute( + inStream, + &m_attribute); // will allocate space for arbitrary header if necessary + NS_ENSURE_SUCCESS(rv, rv); + if (!commaSep) return NS_ERROR_INVALID_ARG; + char* secondCommaSep = PL_strchr(commaSep + 1, ','); + if (commaSep) rv = ParseOperator(commaSep + 1, &m_operator); + NS_ENSURE_SUCCESS(rv, rv); + // convert label filters and saved searches to keyword equivalents + if (secondCommaSep) ParseValue(secondCommaSep + 1); + return NS_OK; +} + +// Looks in the MessageDB for the user specified arbitrary header, if it finds +// the header, it then looks for a match against the value for the header. +nsresult nsMsgSearchTerm::MatchArbitraryHeader( + nsIMsgSearchScopeTerm* scope, uint32_t length /* in lines*/, + const char* charset, bool charsetOverride, nsIMsgDBHdr* msg, + nsIMsgDatabase* db, const nsACString& headers, bool ForFiltering, + bool* pResult) { + NS_ENSURE_ARG_POINTER(pResult); + + *pResult = false; + nsresult rv = NS_OK; + bool matchExpected = m_operator == nsMsgSearchOp::Contains || + m_operator == nsMsgSearchOp::Is || + m_operator == nsMsgSearchOp::BeginsWith || + m_operator == nsMsgSearchOp::EndsWith; + // Initialize result to what we want if we don't find the header at all. + bool result = !matchExpected; + + nsCString dbHdrValue; + msg->GetStringProperty(m_arbitraryHeader.get(), dbHdrValue); + if (!dbHdrValue.IsEmpty()) { + // Match value with the other info. It doesn't check all header occurrences, + // so we use it only if we match and do line by line headers parsing + // otherwise. + rv = MatchRfc2047String(dbHdrValue, charset, charsetOverride, pResult); + if (matchExpected == *pResult) return rv; + + // Preset result in case we don't have access to the headers, like for IMAP. + result = *pResult; + } + + nsMsgBodyHandler* bodyHandler = + new nsMsgBodyHandler(scope, length, msg, db, headers.BeginReading(), + headers.Length(), ForFiltering); + bodyHandler->SetStripHeaders(false); + + nsCString headerFullValue; // Contains matched header value accumulated over + // multiple lines. + nsAutoCString buf; + nsAutoCString curMsgHeader; + bool processingHeaders = true; + + while (processingHeaders) { + nsCString charsetIgnored; + if (bodyHandler->GetNextLine(buf, charsetIgnored) < 0 || + EMPTY_MESSAGE_LINE(buf)) + processingHeaders = + false; // No more lines or empty line terminating headers. + + bool isContinuationHeader = + processingHeaders ? NS_IsAsciiWhitespace(buf.CharAt(0)) : false; + + // If we're not on a continuation header the header value is not empty, + // we have finished accumulating the header value by iterating over all + // header lines. Now we need to check whether the value is a match. + if (!isContinuationHeader && !headerFullValue.IsEmpty()) { + bool stringMatches; + // Match value with the other info. + rv = MatchRfc2047String(headerFullValue, charset, charsetOverride, + &stringMatches); + if (matchExpected == stringMatches) // if we found a match + { + // If we found a match, stop examining the headers. + processingHeaders = false; + result = stringMatches; + } + // Prepare for repeated header of the same type. + headerFullValue.Truncate(); + } + + // We got result or finished processing all lines. + if (!processingHeaders) break; + + char* buf_end = (char*)(buf.get() + buf.Length()); + int headerLength = m_arbitraryHeader.Length(); + + // If the line starts with whitespace, then we use the current header. + if (!isContinuationHeader) { + // Here we start a new header. + uint32_t colonPos = buf.FindChar(':'); + curMsgHeader = StringHead(buf, colonPos); + } + + if (curMsgHeader.Equals(m_arbitraryHeader, + nsCaseInsensitiveCStringComparator)) { + // Process the value: + // Value occurs after the header name or whitespace continuation char. + const char* headerValue = + buf.get() + (isContinuationHeader ? 1 : headerLength); + if (headerValue < buf_end && + headerValue[0] == + ':') // + 1 to account for the colon which is MANDATORY + headerValue++; + + // Strip leading white space. + while (headerValue < buf_end && isspace(*headerValue)) headerValue++; + + // Strip trailing white space. + char* end = buf_end - 1; + while (headerValue < end && isspace(*end)) { + *end = '\0'; + end--; + } + + // Any continuation whitespace is converted to a single space. + if (!headerFullValue.IsEmpty()) headerFullValue.Append(' '); + headerFullValue.Append(nsDependentCString(headerValue)); + } + } + + delete bodyHandler; + *pResult = result; + return rv; +} + +NS_IMETHODIMP nsMsgSearchTerm::MatchHdrProperty(nsIMsgDBHdr* aHdr, + bool* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_ARG_POINTER(aHdr); + + nsCString dbHdrValue; + aHdr->GetStringProperty(m_hdrProperty.get(), dbHdrValue); + return MatchString(dbHdrValue, nullptr, aResult); +} + +NS_IMETHODIMP nsMsgSearchTerm::MatchFolderFlag(nsIMsgDBHdr* aMsgToMatch, + bool* aResult) { + NS_ENSURE_ARG_POINTER(aMsgToMatch); + NS_ENSURE_ARG_POINTER(aResult); + + nsCOMPtr<nsIMsgFolder> msgFolder; + nsresult rv = aMsgToMatch->GetFolder(getter_AddRefs(msgFolder)); + NS_ENSURE_SUCCESS(rv, rv); + uint32_t folderFlags; + msgFolder->GetFlags(&folderFlags); + return MatchStatus(folderFlags, aResult); +} + +NS_IMETHODIMP nsMsgSearchTerm::MatchUint32HdrProperty(nsIMsgDBHdr* aHdr, + bool* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_ARG_POINTER(aHdr); + + nsresult rv = NS_OK; + uint32_t dbHdrValue; + aHdr->GetUint32Property(m_hdrProperty.get(), &dbHdrValue); + + bool result = false; + switch (m_operator) { + case nsMsgSearchOp::IsGreaterThan: + if (dbHdrValue > m_value.u.msgStatus) result = true; + break; + case nsMsgSearchOp::IsLessThan: + if (dbHdrValue < m_value.u.msgStatus) result = true; + break; + case nsMsgSearchOp::Is: + if (dbHdrValue == m_value.u.msgStatus) result = true; + break; + case nsMsgSearchOp::Isnt: + if (dbHdrValue != m_value.u.msgStatus) result = true; + break; + default: + rv = NS_ERROR_FAILURE; + NS_ERROR("invalid compare op for uint"); + } + *aResult = result; + return rv; +} + +nsresult nsMsgSearchTerm::MatchBody(nsIMsgSearchScopeTerm* scope, + uint64_t offset, + uint32_t length /*in lines*/, + const char* folderCharset, nsIMsgDBHdr* msg, + nsIMsgDatabase* db, bool* pResult) { + NS_ENSURE_ARG_POINTER(pResult); + + nsresult rv = NS_OK; + + bool result = false; + *pResult = false; + + // Small hack so we don't look all through a message when someone has + // specified "BODY IS foo". ### Since length is in lines, this is not quite + // right. + if ((length > 0) && + (m_operator == nsMsgSearchOp::Is || m_operator == nsMsgSearchOp::Isnt)) + length = m_value.utf8String.Length(); + + nsMsgBodyHandler* bodyHan = new nsMsgBodyHandler(scope, length, msg, db); + if (!bodyHan) return NS_ERROR_OUT_OF_MEMORY; + + nsAutoCString buf; + bool endOfFile = false; // if retValue == 0, we've hit the end of the file + + // Change the sense of the loop so we don't bail out prematurely + // on negative terms. i.e. opDoesntContain must look at all lines + bool boolContinueLoop; + GetMatchAllBeforeDeciding(&boolContinueLoop); + result = boolContinueLoop; + + nsCString compare; + nsCString charset; + while (!endOfFile && result == boolContinueLoop) { + if (bodyHan->GetNextLine(buf, charset) >= 0) { + bool softLineBreak = false; + // Do in-place decoding of quoted printable + if (bodyHan->IsQP()) { + softLineBreak = StringEndsWith(buf, "="_ns); + MsgStripQuotedPrintable(buf); + // If soft line break, chop off the last char as well. + size_t bufLength = buf.Length(); + if ((bufLength > 0) && softLineBreak) buf.SetLength(bufLength - 1); + } + compare.Append(buf); + // If this line ends with a soft line break, loop around + // and get the next line before looking for the search string. + // This assumes the message can't end on a QP soft line break. + // That seems like a pretty safe assumption. + if (softLineBreak) continue; + if (!compare.IsEmpty()) { + char startChar = (char)compare.CharAt(0); + if (startChar != '\r' && startChar != '\n') { + rv = MatchString(compare, + charset.IsEmpty() ? folderCharset : charset.get(), + &result); + } + compare.Truncate(); + } + } else + endOfFile = true; + } + + delete bodyHan; + *pResult = result; + return rv; +} + +nsresult nsMsgSearchTerm::InitializeAddressBook() { + // the search attribute value has the URI for the address book we need to + // load. we need both the database and the directory. + nsresult rv = NS_OK; + + if (mDirectory) { + nsCString uri; + rv = mDirectory->GetURI(uri); + NS_ENSURE_SUCCESS(rv, rv); + + if (!uri.Equals(m_value.utf8String)) + // clear out the directory....we are no longer pointing to the right one + mDirectory = nullptr; + } + if (!mDirectory) { + nsCOMPtr<nsIAbManager> abManager = + do_GetService("@mozilla.org/abmanager;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = + abManager->GetDirectory(m_value.utf8String, getter_AddRefs(mDirectory)); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +nsresult nsMsgSearchTerm::MatchInAddressBook(const nsAString& aAddress, + bool* pResult) { + nsresult rv = InitializeAddressBook(); + *pResult = false; + + // Some junkmails have empty From: fields. + if (aAddress.IsEmpty()) return rv; + + if (mDirectory) { + nsCOMPtr<nsIAbCard> cardForAddress = nullptr; + rv = mDirectory->CardForEmailAddress(NS_ConvertUTF16toUTF8(aAddress), + getter_AddRefs(cardForAddress)); + if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) return rv; + switch (m_operator) { + case nsMsgSearchOp::IsInAB: + if (cardForAddress) *pResult = true; + break; + case nsMsgSearchOp::IsntInAB: + if (!cardForAddress) *pResult = true; + break; + default: + rv = NS_ERROR_FAILURE; + NS_ERROR("invalid compare op for address book"); + } + } + + return rv; +} + +// *pResult is false when strings don't match, true if they do. +nsresult nsMsgSearchTerm::MatchRfc2047String(const nsACString& rfc2047string, + const char* charset, + bool charsetOverride, + bool* pResult) { + NS_ENSURE_ARG_POINTER(pResult); + + nsresult rv; + nsCOMPtr<nsIMimeConverter> mimeConverter = + do_GetService("@mozilla.org/messenger/mimeconverter;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoString stringToMatch; + rv = mimeConverter->DecodeMimeHeader(PromiseFlatCString(rfc2047string).get(), + charset, charsetOverride, false, + stringToMatch); + NS_ENSURE_SUCCESS(rv, rv); + if (m_operator == nsMsgSearchOp::IsInAB || + m_operator == nsMsgSearchOp::IsntInAB) + return MatchInAddressBook(stringToMatch, pResult); + + return MatchString(stringToMatch, pResult); +} + +// *pResult is false when strings don't match, true if they do. +nsresult nsMsgSearchTerm::MatchString(const nsACString& stringToMatch, + const char* charset, bool* pResult) { + NS_ENSURE_ARG_POINTER(pResult); + + bool result = false; + + nsresult rv = NS_OK; + + // Save some performance for opIsEmpty / opIsntEmpty + if (nsMsgSearchOp::IsEmpty == m_operator) { + if (stringToMatch.IsEmpty()) result = true; + } else if (nsMsgSearchOp::IsntEmpty == m_operator) { + if (!stringToMatch.IsEmpty()) result = true; + } else { + nsAutoString utf16StrToMatch; + rv = NS_ERROR_UNEXPECTED; + if (charset) { + rv = nsMsgI18NConvertToUnicode(nsDependentCString(charset), stringToMatch, + utf16StrToMatch); + } + if (NS_FAILED(rv)) { + // No charset or conversion failed, maybe due to a bad charset, try UTF-8. + if (mozilla::IsUtf8(stringToMatch)) { + CopyUTF8toUTF16(stringToMatch, utf16StrToMatch); + } else { + // Bad luck, let's assume ASCII/windows-1252 then. + CopyASCIItoUTF16(stringToMatch, utf16StrToMatch); + } + } + + rv = MatchString(utf16StrToMatch, &result); + } + + *pResult = result; + return rv; +} + +// *pResult is false when strings don't match, true if they do. +nsresult nsMsgSearchTerm::MatchString(const nsAString& utf16StrToMatch, + bool* pResult) { + NS_ENSURE_ARG_POINTER(pResult); + + bool result = false; + + nsresult rv = NS_OK; + auto needle = m_value.utf16String; + + switch (m_operator) { + case nsMsgSearchOp::Contains: + if (CaseInsensitiveFindInReadable(needle, utf16StrToMatch)) result = true; + break; + case nsMsgSearchOp::DoesntContain: + if (!CaseInsensitiveFindInReadable(needle, utf16StrToMatch)) + result = true; + break; + case nsMsgSearchOp::Is: + if (needle.Equals(utf16StrToMatch, nsCaseInsensitiveStringComparator)) + result = true; + break; + case nsMsgSearchOp::Isnt: + if (!needle.Equals(utf16StrToMatch, nsCaseInsensitiveStringComparator)) + result = true; + break; + case nsMsgSearchOp::IsEmpty: + if (utf16StrToMatch.IsEmpty()) result = true; + break; + case nsMsgSearchOp::IsntEmpty: + if (!utf16StrToMatch.IsEmpty()) result = true; + break; + case nsMsgSearchOp::BeginsWith: + if (StringBeginsWith(utf16StrToMatch, needle, + nsCaseInsensitiveStringComparator)) + result = true; + break; + case nsMsgSearchOp::EndsWith: + if (StringEndsWith(utf16StrToMatch, needle, + nsCaseInsensitiveStringComparator)) + result = true; + break; + default: + rv = NS_ERROR_FAILURE; + NS_ERROR("invalid compare op for matching search results"); + } + + *pResult = result; + return rv; +} + +NS_IMETHODIMP nsMsgSearchTerm::GetMatchAllBeforeDeciding(bool* aResult) { + *aResult = (m_operator == nsMsgSearchOp::DoesntContain || + m_operator == nsMsgSearchOp::Isnt); + return NS_OK; +} + +NS_IMETHODIMP nsMsgSearchTerm::MatchRfc822String(const nsACString& string, + const char* charset, + bool* pResult) { + NS_ENSURE_ARG_POINTER(pResult); + + *pResult = false; + bool result; + + // Change the sense of the loop so we don't bail out prematurely + // on negative terms. i.e. opDoesntContain must look at all recipients + bool boolContinueLoop; + GetMatchAllBeforeDeciding(&boolContinueLoop); + result = boolContinueLoop; + + // If the operator is Contains, then we can cheat and avoid having to parse + // addresses. This does open up potential spurious matches for punctuation + // (e.g., ; or <), but the likelihood of users intending to search for these + // and also being able to match them is rather low. This optimization is not + // applicable to any other search type. + if (m_operator == nsMsgSearchOp::Contains) + return MatchRfc2047String(string, charset, false, pResult); + + nsTArray<nsString> names, addresses; + ExtractAllAddresses(EncodedHeader(string, charset), names, addresses); + uint32_t count = names.Length(); + + nsresult rv = NS_OK; + for (uint32_t i = 0; i < count && result == boolContinueLoop; i++) { + if (m_operator == nsMsgSearchOp::IsInAB || + m_operator == nsMsgSearchOp::IsntInAB) { + rv = MatchInAddressBook(addresses[i], &result); + } else { + rv = MatchString(names[i], &result); + if (boolContinueLoop == result) rv = MatchString(addresses[i], &result); + } + } + *pResult = result; + return rv; +} + +nsresult nsMsgSearchTerm::GetLocalTimes(PRTime a, PRTime b, + PRExplodedTime& aExploded, + PRExplodedTime& bExploded) { + PR_ExplodeTime(a, PR_LocalTimeParameters, &aExploded); + PR_ExplodeTime(b, PR_LocalTimeParameters, &bExploded); + return NS_OK; +} + +nsresult nsMsgSearchTerm::MatchDate(PRTime dateToMatch, bool* pResult) { + NS_ENSURE_ARG_POINTER(pResult); + + nsresult rv = NS_OK; + bool result = false; + + PRExplodedTime tmToMatch, tmThis; + if (NS_SUCCEEDED( + GetLocalTimes(dateToMatch, m_value.u.date, tmToMatch, tmThis))) { + switch (m_operator) { + case nsMsgSearchOp::IsBefore: + if (tmToMatch.tm_year < tmThis.tm_year || + (tmToMatch.tm_year == tmThis.tm_year && + tmToMatch.tm_yday < tmThis.tm_yday)) + result = true; + break; + case nsMsgSearchOp::IsAfter: + if (tmToMatch.tm_year > tmThis.tm_year || + (tmToMatch.tm_year == tmThis.tm_year && + tmToMatch.tm_yday > tmThis.tm_yday)) + result = true; + break; + case nsMsgSearchOp::Is: + if (tmThis.tm_year == tmToMatch.tm_year && + tmThis.tm_month == tmToMatch.tm_month && + tmThis.tm_mday == tmToMatch.tm_mday) + result = true; + break; + case nsMsgSearchOp::Isnt: + if (tmThis.tm_year != tmToMatch.tm_year || + tmThis.tm_month != tmToMatch.tm_month || + tmThis.tm_mday != tmToMatch.tm_mday) + result = true; + break; + default: + rv = NS_ERROR_FAILURE; + NS_ERROR("invalid compare op for dates"); + } + } + *pResult = result; + return rv; +} + +nsresult nsMsgSearchTerm::MatchAge(PRTime msgDate, bool* pResult) { + NS_ENSURE_ARG_POINTER(pResult); + + bool result = false; + nsresult rv = NS_OK; + + PRTime now = PR_Now(); + PRTime cutOffDay = now - m_value.u.age * PR_USEC_PER_DAY; + + bool cutOffDayInTheFuture = m_value.u.age < 0; + + // So now cutOffDay is the PRTime cut-off point. + // Any msg with a time less than that will be past the age. + + switch (m_operator) { + case nsMsgSearchOp::IsGreaterThan: // is older than, or more in the future + if ((!cutOffDayInTheFuture && msgDate < cutOffDay) || + (cutOffDayInTheFuture && msgDate > cutOffDay)) + result = true; + break; + case nsMsgSearchOp::IsLessThan: // is younger than, or less in the future + if ((!cutOffDayInTheFuture && msgDate > cutOffDay) || + (cutOffDayInTheFuture && msgDate < cutOffDay)) + result = true; + break; + case nsMsgSearchOp::Is: + PRExplodedTime msgDateExploded; + PRExplodedTime cutOffDayExploded; + if (NS_SUCCEEDED(GetLocalTimes(msgDate, cutOffDay, msgDateExploded, + cutOffDayExploded))) { + if ((msgDateExploded.tm_mday == cutOffDayExploded.tm_mday) && + (msgDateExploded.tm_month == cutOffDayExploded.tm_month) && + (msgDateExploded.tm_year == cutOffDayExploded.tm_year)) + result = true; + } + break; + default: + rv = NS_ERROR_FAILURE; + NS_ERROR("invalid compare op for msg age"); + } + *pResult = result; + return rv; +} + +nsresult nsMsgSearchTerm::MatchSize(uint32_t sizeToMatch, bool* pResult) { + NS_ENSURE_ARG_POINTER(pResult); + + nsresult rv = NS_OK; + bool result = false; + // We reduce the sizeToMatch rather than supplied size + // as then we can do an exact match on the displayed value + // which will be less confusing to the user. + uint32_t sizeToMatchKB = sizeToMatch; + + if (sizeToMatchKB < 1024) sizeToMatchKB = 1024; + + sizeToMatchKB /= 1024; + + switch (m_operator) { + case nsMsgSearchOp::IsGreaterThan: + if (sizeToMatchKB > m_value.u.size) result = true; + break; + case nsMsgSearchOp::IsLessThan: + if (sizeToMatchKB < m_value.u.size) result = true; + break; + case nsMsgSearchOp::Is: + if (sizeToMatchKB == m_value.u.size) result = true; + break; + default: + rv = NS_ERROR_FAILURE; + NS_ERROR("invalid compare op for size to match"); + } + *pResult = result; + return rv; +} + +nsresult nsMsgSearchTerm::MatchJunkStatus(const char* aJunkScore, + bool* pResult) { + NS_ENSURE_ARG_POINTER(pResult); + + if (m_operator == nsMsgSearchOp::IsEmpty) { + *pResult = !(aJunkScore && *aJunkScore); + return NS_OK; + } + if (m_operator == nsMsgSearchOp::IsntEmpty) { + *pResult = (aJunkScore && *aJunkScore); + return NS_OK; + } + + nsMsgJunkStatus junkStatus; + if (aJunkScore && *aJunkScore) { + junkStatus = (atoi(aJunkScore) == nsIJunkMailPlugin::IS_SPAM_SCORE) + ? nsIJunkMailPlugin::JUNK + : nsIJunkMailPlugin::GOOD; + } else { + // the in UI, we only show "junk" or "not junk" + // unknown, or nsIJunkMailPlugin::UNCLASSIFIED is shown as not junk + // so for the search to work as expected, treat unknown as not junk + junkStatus = nsIJunkMailPlugin::GOOD; + } + + nsresult rv = NS_OK; + bool matches = (junkStatus == m_value.u.junkStatus); + + switch (m_operator) { + case nsMsgSearchOp::Is: + break; + case nsMsgSearchOp::Isnt: + matches = !matches; + break; + default: + rv = NS_ERROR_FAILURE; + matches = false; + NS_ERROR("invalid compare op for junk status"); + } + + *pResult = matches; + return rv; +} + +nsresult nsMsgSearchTerm::MatchJunkScoreOrigin(const char* aJunkScoreOrigin, + bool* pResult) { + NS_ENSURE_ARG_POINTER(pResult); + + bool matches = false; + nsresult rv = NS_OK; + + switch (m_operator) { + case nsMsgSearchOp::Is: + matches = aJunkScoreOrigin && m_value.utf8String.Equals(aJunkScoreOrigin); + break; + case nsMsgSearchOp::Isnt: + matches = + !aJunkScoreOrigin || !m_value.utf8String.Equals(aJunkScoreOrigin); + break; + default: + rv = NS_ERROR_FAILURE; + NS_ERROR("invalid compare op for junk score origin"); + } + + *pResult = matches; + return rv; +} + +nsresult nsMsgSearchTerm::MatchJunkPercent(uint32_t aJunkPercent, + bool* pResult) { + NS_ENSURE_ARG_POINTER(pResult); + + nsresult rv = NS_OK; + bool result = false; + switch (m_operator) { + case nsMsgSearchOp::IsGreaterThan: + if (aJunkPercent > m_value.u.junkPercent) result = true; + break; + case nsMsgSearchOp::IsLessThan: + if (aJunkPercent < m_value.u.junkPercent) result = true; + break; + case nsMsgSearchOp::Is: + if (aJunkPercent == m_value.u.junkPercent) result = true; + break; + default: + rv = NS_ERROR_FAILURE; + NS_ERROR("invalid compare op for junk percent"); + } + *pResult = result; + return rv; +} + +// MatchStatus () is not only used for nsMsgMessageFlags but also for +// nsMsgFolderFlags (both being 'unsigned long') +nsresult nsMsgSearchTerm::MatchStatus(uint32_t statusToMatch, bool* pResult) { + NS_ENSURE_ARG_POINTER(pResult); + + nsresult rv = NS_OK; + bool matches = (statusToMatch & m_value.u.msgStatus); + + // nsMsgSearchOp::Is and nsMsgSearchOp::Isnt are intentionally used as + // Contains and DoesntContain respectively, for legacy reasons. + switch (m_operator) { + case nsMsgSearchOp::Is: + break; + case nsMsgSearchOp::Isnt: + matches = !matches; + break; + default: + rv = NS_ERROR_FAILURE; + matches = false; + NS_ERROR("invalid compare op for msg status"); + } + + *pResult = matches; + return rv; +} + +/* + * MatchKeyword Logic table (*pResult: + is true, - is false) + * + * # Valid Tokens IsEmpty IsntEmpty Contains DoesntContain Is Isnt + * 0 + - - + - + + * Term found? N Y N Y N Y N Y + * 1 - + - + + - - + + - + * >1 - + - + + - - - + + + */ +// look up nsMsgSearchTerm::m_value in space-delimited keywordList +nsresult nsMsgSearchTerm::MatchKeyword(const nsACString& keywordList, + bool* pResult) { + NS_ENSURE_ARG_POINTER(pResult); + + bool matches = false; + + // special-case empty for performance reasons + if (keywordList.IsEmpty()) { + *pResult = m_operator != nsMsgSearchOp::Contains && + m_operator != nsMsgSearchOp::Is && + m_operator != nsMsgSearchOp::IsntEmpty; + return NS_OK; + } + + // check if we can skip expensive valid keywordList test + if (m_operator == nsMsgSearchOp::DoesntContain || + m_operator == nsMsgSearchOp::Contains) { + nsCString keywordString(keywordList); + const uint32_t kKeywordLen = m_value.utf8String.Length(); + const char* matchStart = + PL_strstr(keywordString.get(), m_value.utf8String.get()); + while (matchStart) { + // For a real match, matchStart must be the start of the keywordList or + // preceded by a space and matchEnd must point to a \0 or space. + const char* matchEnd = matchStart + kKeywordLen; + if ((matchStart == keywordString.get() || matchStart[-1] == ' ') && + (!*matchEnd || *matchEnd == ' ')) { + // found the keyword + *pResult = m_operator == nsMsgSearchOp::Contains; + return NS_OK; + } + // no match yet, so search on + matchStart = PL_strstr(matchEnd, m_value.utf8String.get()); + } + // keyword not found + *pResult = m_operator == nsMsgSearchOp::DoesntContain; + return NS_OK; + } + + // Only accept valid keys in tokens. + nsresult rv = NS_OK; + nsTArray<nsCString> keywordArray; + ParseString(keywordList, ' ', keywordArray); + nsCOMPtr<nsIMsgTagService> tagService( + do_GetService("@mozilla.org/messenger/tagservice;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // Loop through tokens in keywords + uint32_t count = keywordArray.Length(); + for (uint32_t i = 0; i < count; i++) { + // is this token a valid tag? Otherwise ignore it + bool isValid; + rv = tagService->IsValidKey(keywordArray[i], &isValid); + NS_ENSURE_SUCCESS(rv, rv); + + if (isValid) { + // IsEmpty fails on any valid token + if (m_operator == nsMsgSearchOp::IsEmpty) { + *pResult = false; + return rv; + } + + // IsntEmpty succeeds on any valid token + if (m_operator == nsMsgSearchOp::IsntEmpty) { + *pResult = true; + return rv; + } + + // Does this valid tag key match our search term? + matches = keywordArray[i].Equals(m_value.utf8String); + + // Is or Isn't partly determined on a single unmatched token + if (!matches) { + if (m_operator == nsMsgSearchOp::Is) { + *pResult = false; + return rv; + } + if (m_operator == nsMsgSearchOp::Isnt) { + *pResult = true; + return rv; + } + } + } + } + + if (m_operator == nsMsgSearchOp::Is) { + *pResult = matches; + return NS_OK; + } + + if (m_operator == nsMsgSearchOp::Isnt) { + *pResult = !matches; + return NS_OK; + } + + if (m_operator == nsMsgSearchOp::IsEmpty) { + *pResult = true; + return NS_OK; + } + + if (m_operator == nsMsgSearchOp::IsntEmpty) { + *pResult = false; + return NS_OK; + } + + // no valid match operator found + *pResult = false; + NS_ERROR("invalid compare op for msg status"); + return NS_ERROR_FAILURE; +} + +nsresult nsMsgSearchTerm::MatchPriority(nsMsgPriorityValue priorityToMatch, + bool* pResult) { + NS_ENSURE_ARG_POINTER(pResult); + + nsresult rv = NS_OK; + bool result = false; + + // Use this ugly little hack to get around the fact that enums don't have + // integer compare operators + int p1 = (priorityToMatch == nsMsgPriority::none) ? (int)nsMsgPriority::normal + : (int)priorityToMatch; + int p2 = (int)m_value.u.priority; + + switch (m_operator) { + case nsMsgSearchOp::IsHigherThan: + if (p1 > p2) result = true; + break; + case nsMsgSearchOp::IsLowerThan: + if (p1 < p2) result = true; + break; + case nsMsgSearchOp::Is: + if (p1 == p2) result = true; + break; + case nsMsgSearchOp::Isnt: + if (p1 != p2) result = true; + break; + default: + rv = NS_ERROR_FAILURE; + NS_ERROR("invalid compare op for priority"); + } + *pResult = result; + return rv; +} + +// match a custom search term +NS_IMETHODIMP nsMsgSearchTerm::MatchCustom(nsIMsgDBHdr* aHdr, bool* pResult) { + NS_ENSURE_ARG_POINTER(pResult); + + nsresult rv; + nsCOMPtr<nsIMsgFilterService> filterService = + do_GetService("@mozilla.org/messenger/services/filters;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgSearchCustomTerm> customTerm; + rv = filterService->GetCustomTerm(m_customId, getter_AddRefs(customTerm)); + NS_ENSURE_SUCCESS(rv, rv); + + if (customTerm) + return customTerm->Match(aHdr, m_value.utf8String, m_operator, pResult); + *pResult = false; // default to no match if term is missing + return NS_ERROR_FAILURE; // missing custom term +} + +// set the id of a custom search term +NS_IMETHODIMP nsMsgSearchTerm::SetCustomId(const nsACString& aId) { + m_customId = aId; + return NS_OK; +} + +// get the id of a custom search term +NS_IMETHODIMP nsMsgSearchTerm::GetCustomId(nsACString& aResult) { + aResult = m_customId; + return NS_OK; +} + +NS_IMPL_GETSET(nsMsgSearchTerm, Attrib, nsMsgSearchAttribValue, m_attribute) +NS_IMPL_GETSET(nsMsgSearchTerm, Op, nsMsgSearchOpValue, m_operator) +NS_IMPL_GETSET(nsMsgSearchTerm, MatchAll, bool, m_matchAll) + +NS_IMETHODIMP +nsMsgSearchTerm::GetValue(nsIMsgSearchValue** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + NS_ADDREF(*aResult = new nsMsgSearchValueImpl(&m_value)); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchTerm::SetValue(nsIMsgSearchValue* aValue) { + nsMsgResultElement::AssignValues(aValue, &m_value); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchTerm::GetBooleanAnd(bool* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = (m_booleanOp == nsMsgSearchBooleanOp::BooleanAND); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchTerm::SetBooleanAnd(bool aValue) { + if (aValue) + m_booleanOp = nsMsgSearchBooleanOperator(nsMsgSearchBooleanOp::BooleanAND); + else + m_booleanOp = nsMsgSearchBooleanOperator(nsMsgSearchBooleanOp::BooleanOR); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchTerm::GetArbitraryHeader(nsACString& aResult) { + aResult = m_arbitraryHeader; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchTerm::SetArbitraryHeader(const nsACString& aValue) { + m_arbitraryHeader = aValue; + ToLowerCaseExceptSpecials(m_arbitraryHeader); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchTerm::GetHdrProperty(nsACString& aResult) { + aResult = m_hdrProperty; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchTerm::SetHdrProperty(const nsACString& aValue) { + m_hdrProperty = aValue; + ToLowerCaseExceptSpecials(m_hdrProperty); + return NS_OK; +} + +NS_IMPL_GETSET(nsMsgSearchTerm, BeginsGrouping, bool, mBeginsGrouping) +NS_IMPL_GETSET(nsMsgSearchTerm, EndsGrouping, bool, mEndsGrouping) + +// +// Certain possible standard values of a message database row also sometimes +// appear as header values. To prevent a naming collision, we use all +// lower case for the standard headers, and first capital when those +// same strings are requested as arbitrary headers. This routine is used +// when setting arbitrary headers. +// +void nsMsgSearchTerm::ToLowerCaseExceptSpecials(nsACString& aValue) { + if (aValue.LowerCaseEqualsLiteral("sender")) + aValue.AssignLiteral("Sender"); + else if (aValue.LowerCaseEqualsLiteral("date")) + aValue.AssignLiteral("Date"); + else if (aValue.LowerCaseEqualsLiteral("status")) + aValue.AssignLiteral("Status"); + else + ToLowerCase(aValue); +} + +//----------------------------------------------------------------------------- +// nsMsgSearchScopeTerm implementation +//----------------------------------------------------------------------------- +nsMsgSearchScopeTerm::nsMsgSearchScopeTerm(nsIMsgSearchSession* session, + nsMsgSearchScopeValue attribute, + nsIMsgFolder* folder) { + m_attribute = attribute; + m_folder = folder; + m_searchServer = true; + m_searchSession = do_GetWeakReference(session); +} + +nsMsgSearchScopeTerm::nsMsgSearchScopeTerm() { m_searchServer = true; } + +nsMsgSearchScopeTerm::~nsMsgSearchScopeTerm() { + if (m_inputStream) m_inputStream->Close(); + m_inputStream = nullptr; +} + +NS_IMPL_ISUPPORTS(nsMsgSearchScopeTerm, nsIMsgSearchScopeTerm) + +NS_IMETHODIMP +nsMsgSearchScopeTerm::GetFolder(nsIMsgFolder** aResult) { + NS_IF_ADDREF(*aResult = m_folder); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchScopeTerm::GetSearchSession(nsIMsgSearchSession** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + nsCOMPtr<nsIMsgSearchSession> searchSession = + do_QueryReferent(m_searchSession); + searchSession.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchScopeTerm::GetInputStream(nsIMsgDBHdr* aMsgHdr, + nsIInputStream** aInputStream) { + NS_ENSURE_ARG_POINTER(aInputStream); + NS_ENSURE_ARG_POINTER(aMsgHdr); + NS_ENSURE_TRUE(m_folder, NS_ERROR_NULL_POINTER); + nsresult rv = + m_folder->GetMsgInputStream(aMsgHdr, getter_AddRefs(m_inputStream)); + NS_ENSURE_SUCCESS(rv, rv); + NS_IF_ADDREF(*aInputStream = m_inputStream); + return rv; +} + +NS_IMETHODIMP nsMsgSearchScopeTerm::CloseInputStream() { + if (m_inputStream) { + m_inputStream->Close(); + m_inputStream = nullptr; + } + return NS_OK; +} + +nsresult nsMsgSearchScopeTerm::TimeSlice(bool* aDone) { + return m_adapter->Search(aDone); +} + +nsresult nsMsgSearchScopeTerm::InitializeAdapter( + nsTArray<RefPtr<nsIMsgSearchTerm>> const& termList) { + if (m_adapter) return NS_OK; + + nsresult rv = NS_OK; + + switch (m_attribute) { + case nsMsgSearchScope::onlineMail: + m_adapter = new nsMsgSearchOnlineMail(this, termList); + break; + case nsMsgSearchScope::offlineMail: + case nsMsgSearchScope::onlineManual: + m_adapter = new nsMsgSearchOfflineMail(this, termList); + break; + case nsMsgSearchScope::newsEx: + NS_ASSERTION(false, "not supporting newsEx yet"); + break; + case nsMsgSearchScope::news: + m_adapter = new nsMsgSearchNews(this, termList); + break; + case nsMsgSearchScope::allSearchableGroups: + NS_ASSERTION(false, "not supporting allSearchableGroups yet"); + break; + case nsMsgSearchScope::LDAP: + NS_ASSERTION(false, "not supporting LDAP yet"); + break; + case nsMsgSearchScope::localNews: + case nsMsgSearchScope::localNewsJunk: + case nsMsgSearchScope::localNewsBody: + case nsMsgSearchScope::localNewsJunkBody: + m_adapter = new nsMsgSearchOfflineNews(this, termList); + break; + default: + NS_ASSERTION(false, "invalid scope"); + rv = NS_ERROR_FAILURE; + } + + if (m_adapter) rv = m_adapter->ValidateTerms(); + + return rv; +} + +char* nsMsgSearchScopeTerm::GetStatusBarName() { return nullptr; } + +//----------------------------------------------------------------------------- +// nsMsgResultElement implementation +//----------------------------------------------------------------------------- + +nsMsgResultElement::nsMsgResultElement(nsIMsgSearchAdapter* adapter) { + m_adapter = adapter; +} + +nsMsgResultElement::~nsMsgResultElement() {} + +nsresult nsMsgResultElement::AddValue(nsIMsgSearchValue* value) { + m_valueList.AppendElement(value); + return NS_OK; +} + +nsresult nsMsgResultElement::AddValue(nsMsgSearchValue* value) { + nsMsgSearchValueImpl* valueImpl = new nsMsgSearchValueImpl(value); + delete value; // we keep the nsIMsgSearchValue, not + // the nsMsgSearchValue + return AddValue(valueImpl); +} + +nsresult nsMsgResultElement::AssignValues(nsIMsgSearchValue* src, + nsMsgSearchValue* dst) { + NS_ENSURE_ARG_POINTER(src); + NS_ENSURE_ARG_POINTER(dst); + // Yes, this could be an operator overload, but nsMsgSearchValue is totally + // public, so I'd have to define a derived class with nothing by operator=, + // and that seems like a bit much + nsresult rv = NS_OK; + src->GetAttrib(&dst->attribute); + switch (dst->attribute) { + case nsMsgSearchAttrib::Priority: + rv = src->GetPriority(&dst->u.priority); + break; + case nsMsgSearchAttrib::Date: + rv = src->GetDate(&dst->u.date); + break; + case nsMsgSearchAttrib::HasAttachmentStatus: + case nsMsgSearchAttrib::MsgStatus: + case nsMsgSearchAttrib::FolderFlag: + case nsMsgSearchAttrib::Uint32HdrProperty: + rv = src->GetStatus(&dst->u.msgStatus); + break; + case nsMsgSearchAttrib::MessageKey: + rv = src->GetMsgKey(&dst->u.key); + break; + case nsMsgSearchAttrib::AgeInDays: + rv = src->GetAge(&dst->u.age); + break; + case nsMsgSearchAttrib::JunkStatus: + rv = src->GetJunkStatus(&dst->u.junkStatus); + break; + case nsMsgSearchAttrib::JunkPercent: + rv = src->GetJunkPercent(&dst->u.junkPercent); + break; + case nsMsgSearchAttrib::Size: + rv = src->GetSize(&dst->u.size); + break; + default: + if (dst->attribute < nsMsgSearchAttrib::kNumMsgSearchAttributes) { + NS_ASSERTION(IS_STRING_ATTRIBUTE(dst->attribute), + "assigning non-string result"); + nsString unicodeString; + rv = src->GetStr(unicodeString); + CopyUTF16toUTF8(unicodeString, dst->utf8String); + dst->utf16String = unicodeString; + } else + rv = NS_ERROR_INVALID_ARG; + } + return rv; +} + +nsresult nsMsgResultElement::GetValue(nsMsgSearchAttribValue attrib, + nsMsgSearchValue** outValue) const { + nsresult rv = NS_OK; + *outValue = NULL; + + for (uint32_t i = 0; i < m_valueList.Length() && NS_FAILED(rv); i++) { + nsMsgSearchAttribValue valueAttribute; + m_valueList[i]->GetAttrib(&valueAttribute); + if (attrib == valueAttribute) { + *outValue = new nsMsgSearchValue; + if (*outValue) { + rv = AssignValues(m_valueList[i], *outValue); + // Now this is really strange! What is this assignment doing here? + rv = NS_OK; + } else + rv = NS_ERROR_OUT_OF_MEMORY; + } + } + return rv; +} + +nsresult nsMsgResultElement::GetPrettyName(nsMsgSearchValue** value) { + return GetValue(nsMsgSearchAttrib::Location, value); +} + +nsresult nsMsgResultElement::Open(void* window) { + return NS_ERROR_NULL_POINTER; +} diff --git a/comm/mailnews/search/src/nsMsgSearchValue.cpp b/comm/mailnews/search/src/nsMsgSearchValue.cpp new file mode 100644 index 0000000000..55f88b5fd2 --- /dev/null +++ b/comm/mailnews/search/src/nsMsgSearchValue.cpp @@ -0,0 +1,96 @@ +/* -*- 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 "MailNewsTypes.h" +#include "nsMsgSearchValue.h" +#include "nsIMsgFolder.h" +#include "nsMsgUtils.h" +#include "nsString.h" + +nsMsgSearchValueImpl::nsMsgSearchValueImpl(nsMsgSearchValue* aInitialValue) { + mValue = *aInitialValue; +} + +nsMsgSearchValueImpl::~nsMsgSearchValueImpl() {} + +NS_IMPL_ISUPPORTS(nsMsgSearchValueImpl, nsIMsgSearchValue) + +NS_IMPL_GETSET(nsMsgSearchValueImpl, Priority, nsMsgPriorityValue, + mValue.u.priority) +NS_IMPL_GETSET(nsMsgSearchValueImpl, Status, uint32_t, mValue.u.msgStatus) +NS_IMPL_GETSET(nsMsgSearchValueImpl, Size, uint32_t, mValue.u.size) +NS_IMPL_GETSET(nsMsgSearchValueImpl, MsgKey, nsMsgKey, mValue.u.key) +NS_IMPL_GETSET(nsMsgSearchValueImpl, Age, int32_t, mValue.u.age) +NS_IMPL_GETSET(nsMsgSearchValueImpl, Date, PRTime, mValue.u.date) +NS_IMPL_GETSET(nsMsgSearchValueImpl, Attrib, nsMsgSearchAttribValue, + mValue.attribute) +NS_IMPL_GETSET(nsMsgSearchValueImpl, JunkStatus, uint32_t, mValue.u.junkStatus) +NS_IMPL_GETSET(nsMsgSearchValueImpl, JunkPercent, uint32_t, + mValue.u.junkPercent) + +NS_IMETHODIMP +nsMsgSearchValueImpl::GetFolder(nsIMsgFolder** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_TRUE(mValue.attribute == nsMsgSearchAttrib::FolderInfo, + NS_ERROR_ILLEGAL_VALUE); + NS_IF_ADDREF(*aResult = mValue.u.folder); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchValueImpl::SetFolder(nsIMsgFolder* aValue) { + NS_ENSURE_TRUE(mValue.attribute == nsMsgSearchAttrib::FolderInfo, + NS_ERROR_ILLEGAL_VALUE); + mValue.u.folder = aValue; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchValueImpl::GetStr(nsAString& aResult) { + NS_ENSURE_TRUE(IS_STRING_ATTRIBUTE(mValue.attribute), NS_ERROR_ILLEGAL_VALUE); + aResult = mValue.utf16String; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchValueImpl::SetStr(const nsAString& aValue) { + NS_ENSURE_TRUE(IS_STRING_ATTRIBUTE(mValue.attribute), NS_ERROR_ILLEGAL_VALUE); + CopyUTF16toUTF8(aValue, mValue.utf8String); + mValue.utf16String = aValue; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSearchValueImpl::ToString(nsAString& aResult) { + aResult.AssignLiteral("[nsIMsgSearchValue: "); + if (IS_STRING_ATTRIBUTE(mValue.attribute)) { + aResult.Append(mValue.utf16String); + return NS_OK; + } + + switch (mValue.attribute) { + case nsMsgSearchAttrib::Priority: + case nsMsgSearchAttrib::Date: + case nsMsgSearchAttrib::MsgStatus: + case nsMsgSearchAttrib::MessageKey: + case nsMsgSearchAttrib::Size: + case nsMsgSearchAttrib::AgeInDays: + case nsMsgSearchAttrib::FolderInfo: + case nsMsgSearchAttrib::JunkStatus: + case nsMsgSearchAttrib::JunkPercent: { + nsAutoString tempInt; + tempInt.AppendInt(mValue.attribute); + + aResult.AppendLiteral("type="); + aResult.Append(tempInt); + } break; + default: + NS_ERROR("Unknown search value type"); + } + + aResult.Append(']'); + + return NS_OK; +} diff --git a/comm/mailnews/search/src/nsMsgSearchValue.h b/comm/mailnews/search/src/nsMsgSearchValue.h new file mode 100644 index 0000000000..225ee64760 --- /dev/null +++ b/comm/mailnews/search/src/nsMsgSearchValue.h @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef __nsMsgSearchValue_h +#define __nsMsgSearchValue_h + +#include "nsIMsgSearchValue.h" +#include "nsMsgSearchCore.h" + +class nsMsgSearchValueImpl : public nsIMsgSearchValue { + public: + explicit nsMsgSearchValueImpl(nsMsgSearchValue* aInitialValue); + + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGSEARCHVALUE + + private: + virtual ~nsMsgSearchValueImpl(); + + nsMsgSearchValue mValue; +}; + +#endif |