summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/search
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--comm/mailnews/search/content/CustomHeaders.js194
-rw-r--r--comm/mailnews/search/content/CustomHeaders.xhtml61
-rw-r--r--comm/mailnews/search/content/FilterEditor.js809
-rw-r--r--comm/mailnews/search/content/FilterEditor.xhtml136
-rw-r--r--comm/mailnews/search/content/searchTerm.inc.xhtml27
-rw-r--r--comm/mailnews/search/content/searchTerm.js568
-rw-r--r--comm/mailnews/search/content/searchWidgets.js1779
-rw-r--r--comm/mailnews/search/content/viewLog.js38
-rw-r--r--comm/mailnews/search/content/viewLog.xhtml65
-rw-r--r--comm/mailnews/search/public/moz.build37
-rw-r--r--comm/mailnews/search/public/nsIMsgFilter.idl124
-rw-r--r--comm/mailnews/search/public/nsIMsgFilterCustomAction.idl88
-rw-r--r--comm/mailnews/search/public/nsIMsgFilterHitNotify.idl26
-rw-r--r--comm/mailnews/search/public/nsIMsgFilterList.idl115
-rw-r--r--comm/mailnews/search/public/nsIMsgFilterPlugin.idl331
-rw-r--r--comm/mailnews/search/public/nsIMsgFilterService.idl102
-rw-r--r--comm/mailnews/search/public/nsIMsgOperationListener.idl17
-rw-r--r--comm/mailnews/search/public/nsIMsgSearchAdapter.idl41
-rw-r--r--comm/mailnews/search/public/nsIMsgSearchCustomTerm.idl75
-rw-r--r--comm/mailnews/search/public/nsIMsgSearchNotify.idl30
-rw-r--r--comm/mailnews/search/public/nsIMsgSearchScopeTerm.idl19
-rw-r--r--comm/mailnews/search/public/nsIMsgSearchSession.idl130
-rw-r--r--comm/mailnews/search/public/nsIMsgSearchTerm.idl153
-rw-r--r--comm/mailnews/search/public/nsIMsgSearchValidityManager.idl26
-rw-r--r--comm/mailnews/search/public/nsIMsgSearchValidityTable.idl32
-rw-r--r--comm/mailnews/search/public/nsIMsgSearchValue.idl35
-rw-r--r--comm/mailnews/search/public/nsIMsgTraitService.idl182
-rw-r--r--comm/mailnews/search/public/nsMsgBodyHandler.h110
-rw-r--r--comm/mailnews/search/public/nsMsgFilterCore.idl62
-rw-r--r--comm/mailnews/search/public/nsMsgResultElement.h40
-rw-r--r--comm/mailnews/search/public/nsMsgSearchAdapter.h242
-rw-r--r--comm/mailnews/search/public/nsMsgSearchBoolExpression.h110
-rw-r--r--comm/mailnews/search/public/nsMsgSearchCore.idl206
-rw-r--r--comm/mailnews/search/public/nsMsgSearchScopeTerm.h44
-rw-r--r--comm/mailnews/search/public/nsMsgSearchTerm.h89
-rw-r--r--comm/mailnews/search/src/Bogofilter.sfd14
-rw-r--r--comm/mailnews/search/src/DSPAM.sfd14
-rw-r--r--comm/mailnews/search/src/Habeas.sfd8
-rw-r--r--comm/mailnews/search/src/MsgTraitService.jsm199
-rw-r--r--comm/mailnews/search/src/POPFile.sfd14
-rw-r--r--comm/mailnews/search/src/PeriodicFilterManager.jsm202
-rw-r--r--comm/mailnews/search/src/SpamAssassin.sfd14
-rw-r--r--comm/mailnews/search/src/SpamCatcher.sfd14
-rw-r--r--comm/mailnews/search/src/SpamPal.sfd14
-rw-r--r--comm/mailnews/search/src/components.conf40
-rw-r--r--comm/mailnews/search/src/moz.build37
-rw-r--r--comm/mailnews/search/src/nsMsgBodyHandler.cpp464
-rw-r--r--comm/mailnews/search/src/nsMsgFilter.cpp864
-rw-r--r--comm/mailnews/search/src/nsMsgFilter.h100
-rw-r--r--comm/mailnews/search/src/nsMsgFilterList.cpp1207
-rw-r--r--comm/mailnews/search/src/nsMsgFilterList.h74
-rw-r--r--comm/mailnews/search/src/nsMsgFilterService.cpp1374
-rw-r--r--comm/mailnews/search/src/nsMsgFilterService.h44
-rw-r--r--comm/mailnews/search/src/nsMsgImapSearch.cpp991
-rw-r--r--comm/mailnews/search/src/nsMsgLocalSearch.cpp919
-rw-r--r--comm/mailnews/search/src/nsMsgLocalSearch.h90
-rw-r--r--comm/mailnews/search/src/nsMsgSearchAdapter.cpp1109
-rw-r--r--comm/mailnews/search/src/nsMsgSearchImap.h34
-rw-r--r--comm/mailnews/search/src/nsMsgSearchNews.cpp452
-rw-r--r--comm/mailnews/search/src/nsMsgSearchNews.h54
-rw-r--r--comm/mailnews/search/src/nsMsgSearchSession.cpp576
-rw-r--r--comm/mailnews/search/src/nsMsgSearchSession.h87
-rw-r--r--comm/mailnews/search/src/nsMsgSearchTerm.cpp1797
-rw-r--r--comm/mailnews/search/src/nsMsgSearchValue.cpp96
-rw-r--r--comm/mailnews/search/src/nsMsgSearchValue.h25
-rw-r--r--comm/mailnews/search/test/moz.build6
-rw-r--r--comm/mailnews/search/test/unit/head_mailbase.js23
-rw-r--r--comm/mailnews/search/test/unit/test_base64_decoding.js94
-rw-r--r--comm/mailnews/search/test/unit/test_bug366491.js110
-rw-r--r--comm/mailnews/search/test/unit/test_bug404489.js202
-rw-r--r--comm/mailnews/search/test/unit/test_copyThenMoveManual.js116
-rw-r--r--comm/mailnews/search/test/unit/test_junkWhitelisting.js204
-rw-r--r--comm/mailnews/search/test/unit/test_quarantineFilterMove.js181
-rw-r--r--comm/mailnews/search/test/unit/test_search.js623
-rw-r--r--comm/mailnews/search/test/unit/test_searchAddressInAb.js337
-rw-r--r--comm/mailnews/search/test/unit/test_searchBody.js294
-rw-r--r--comm/mailnews/search/test/unit/test_searchBoolean.js239
-rw-r--r--comm/mailnews/search/test/unit/test_searchChaining.js90
-rw-r--r--comm/mailnews/search/test/unit/test_searchCustomTerm.js112
-rw-r--r--comm/mailnews/search/test/unit/test_searchJunk.js322
-rw-r--r--comm/mailnews/search/test/unit/test_searchLocalizationStrings.js61
-rw-r--r--comm/mailnews/search/test/unit/test_searchTag.js490
-rw-r--r--comm/mailnews/search/test/unit/test_searchUint32HdrProperty.js141
-rw-r--r--comm/mailnews/search/test/unit/xpcshell.ini20
84 files changed, 20735 insertions, 0 deletions
diff --git a/comm/mailnews/search/content/CustomHeaders.js b/comm/mailnews/search/content/CustomHeaders.js
new file mode 100644
index 0000000000..4bfc4a3b78
--- /dev/null
+++ b/comm/mailnews/search/content/CustomHeaders.js
@@ -0,0 +1,194 @@
+/* -*- 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/. */
+
+var gAddButton;
+var gRemoveButton;
+var gHeaderInputElement;
+var gArrayHdrs;
+var gHdrsList;
+var gContainer;
+var gFilterBundle = null;
+var gCustomBundle = null;
+
+window.addEventListener("DOMContentLoaded", onLoad);
+document.addEventListener("dialogaccept", onOk);
+document.addEventListener("dialogextra1", onAddHeader);
+document.addEventListener("dialogextra2", onRemoveHeader);
+
+function onLoad() {
+ let hdrs = Services.prefs.getCharPref("mailnews.customHeaders");
+ gHeaderInputElement = document.getElementById("headerInput");
+ gHeaderInputElement.focus();
+
+ gHdrsList = document.getElementById("headerList");
+ gArrayHdrs = [];
+ gAddButton = document.getElementById("addButton");
+ gRemoveButton = document.getElementById("removeButton");
+
+ initializeDialog(hdrs);
+ updateAddButton(true);
+ updateRemoveButton();
+}
+
+function initializeDialog(hdrs) {
+ if (hdrs) {
+ hdrs = hdrs.replace(/\s+/g, ""); // remove white spaces before splitting
+ gArrayHdrs = hdrs.split(":");
+ for (var i = 0; i < gArrayHdrs.length; i++) {
+ if (!gArrayHdrs[i]) {
+ // Remove any null elements.
+ gArrayHdrs.splice(i, 1);
+ }
+ }
+ initializeRows();
+ }
+}
+
+function initializeRows() {
+ for (var i = 0; i < gArrayHdrs.length; i++) {
+ addRow(TrimString(gArrayHdrs[i]));
+ }
+}
+
+function onTextInput() {
+ // enable the add button if the user has started to type text
+ updateAddButton(gHeaderInputElement.value == "");
+}
+
+function onOk() {
+ if (gArrayHdrs.length) {
+ var hdrs;
+ if (gArrayHdrs.length == 1) {
+ hdrs = gArrayHdrs;
+ } else {
+ hdrs = gArrayHdrs.join(": ");
+ }
+ Services.prefs.setCharPref("mailnews.customHeaders", hdrs);
+ // flush prefs to disk, in case we crash, to avoid dataloss and problems with filters that use the custom headers
+ Services.prefs.savePrefFile(null);
+ } else {
+ Services.prefs.clearUserPref("mailnews.customHeaders"); // clear the pref, no custom headers
+ }
+
+ window.arguments[0].selectedVal = gHdrsList.selectedItem
+ ? gHdrsList.selectedItem.label
+ : null;
+}
+
+function customHeaderOverflow() {
+ var nsMsgSearchAttrib = Ci.nsMsgSearchAttrib;
+ if (
+ gArrayHdrs.length >=
+ nsMsgSearchAttrib.kNumMsgSearchAttributes -
+ nsMsgSearchAttrib.OtherHeader -
+ 1
+ ) {
+ if (!gFilterBundle) {
+ gFilterBundle = document.getElementById("bundle_filter");
+ }
+
+ var alertText = gFilterBundle.getString("customHeaderOverflow");
+ Services.prompt.alert(window, null, alertText);
+ return true;
+ }
+ return false;
+}
+
+function onAddHeader() {
+ var newHdr = TrimString(gHeaderInputElement.value);
+
+ if (!isRFC2822Header(newHdr)) {
+ // if user entered an invalid rfc822 header field name, bail out.
+ if (!gCustomBundle) {
+ gCustomBundle = document.getElementById("bundle_custom");
+ }
+
+ var alertText = gCustomBundle.getString("colonInHeaderName");
+ Services.prompt.alert(window, null, alertText);
+ return;
+ }
+
+ gHeaderInputElement.value = "";
+ if (!newHdr || customHeaderOverflow()) {
+ return;
+ }
+ if (!duplicateHdrExists(newHdr)) {
+ gArrayHdrs[gArrayHdrs.length] = newHdr;
+ var newItem = addRow(newHdr);
+ gHdrsList.selectItem(newItem); // make sure the new entry is selected in the tree
+ // now disable the add button
+ updateAddButton(true);
+ gHeaderInputElement.focus(); // refocus the input field for the next custom header
+ }
+}
+
+function isRFC2822Header(hdr) {
+ var charCode;
+ for (var i = 0; i < hdr.length; i++) {
+ charCode = hdr.charCodeAt(i);
+ // 58 is for colon and 33 and 126 are us-ascii bounds that should be used for header field name, as per rfc2822
+
+ if (charCode < 33 || charCode == 58 || charCode > 126) {
+ return false;
+ }
+ }
+ return true;
+}
+
+function duplicateHdrExists(hdr) {
+ for (var i = 0; i < gArrayHdrs.length; i++) {
+ if (gArrayHdrs[i] == hdr) {
+ return true;
+ }
+ }
+ return false;
+}
+
+function onRemoveHeader() {
+ var listitem = gHdrsList.selectedItems[0];
+ if (!listitem) {
+ return;
+ }
+ listitem.remove();
+ var selectedHdr = listitem.firstElementChild.getAttribute("value").trim();
+ for (let i = 0; i < gArrayHdrs.length; i++) {
+ if (gArrayHdrs[i] == selectedHdr) {
+ gArrayHdrs.splice(i, 1);
+ break;
+ }
+ }
+}
+
+function addRow(newHdr) {
+ return gHdrsList.appendItem(newHdr, "");
+}
+
+function updateAddButton(aDisable) {
+ // only update the button if the disabled state changed
+ if (aDisable == gAddButton.disabled) {
+ return;
+ }
+
+ gAddButton.disabled = aDisable;
+ document.querySelector("dialog").defaultButton = aDisable
+ ? "accept"
+ : "extra1";
+}
+
+function updateRemoveButton() {
+ var headerSelected = gHdrsList.selectedItems.length > 0;
+ gRemoveButton.disabled = !headerSelected;
+ if (gRemoveButton.disabled) {
+ gHeaderInputElement.focus();
+ }
+}
+
+// Remove whitespace from both ends of a string
+function TrimString(string) {
+ if (!string) {
+ return "";
+ }
+ return string.trim();
+}
diff --git a/comm/mailnews/search/content/CustomHeaders.xhtml b/comm/mailnews/search/content/CustomHeaders.xhtml
new file mode 100644
index 0000000000..9a2ac8468f
--- /dev/null
+++ b/comm/mailnews/search/content/CustomHeaders.xhtml
@@ -0,0 +1,61 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+#ifdef MOZ_THUNDERBIRD
+<?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
+#else
+<?xml-stylesheet href="chrome://communicator/skin/" type="text/css"?>
+#endif
+<?xml-stylesheet type="text/css" href="chrome://messenger/skin/input-fields.css"?>
+
+<!DOCTYPE html SYSTEM "chrome://messenger/locale/CustomHeaders.dtd">
+<html id="customHeaderDialog" xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ width="450" height="375"
+ persist="width height screenX screenY"
+ scrolling="false">
+<head>
+ <title>&window.title;</title>
+ <link rel="localization" href="branding/brand.ftl" />
+ <script defer="defer" src="chrome://messenger/content/globalOverlay.js"></script>
+ <script defer="defer" src="chrome://global/content/editMenuOverlay.js"></script>
+ <script defer="defer" src="chrome://messenger/content/CustomHeaders.js"></script>
+</head>
+<html:body xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<dialog buttons="accept,cancel,extra1,extra2">
+ <stringbundle id="bundle_filter" src="chrome://messenger/locale/filter.properties"/>
+ <stringbundle id="bundle_custom" src="chrome://messenger/locale/custom.properties"/>
+
+ <hbox flex="1">
+ <vbox flex="1">
+ <label id="headerInputLabel"
+ accesskey="&newMsgHeader.accesskey;"
+ control="headerInput"
+ value="&newMsgHeader.label;"/>
+ <html:input id="headerInput"
+ type="text"
+ aria-labelledby="headerInputLabel"
+ class="input-inline"
+ oninput="onTextInput();"/>
+ <richlistbox id="headerList"
+ class="theme-listbox"
+ flex="1"
+ onselect="updateRemoveButton();" />
+ </vbox>
+ <vbox>
+ <label value=""/>
+ <button id="addButton"
+ label="&addButton.label;"
+ accesskey="&addButton.accesskey;"
+ dlgtype="extra1"/>
+ <button id="removeButton"
+ label="&removeButton.label;"
+ accesskey="&removeButton.accesskey;"
+ dlgtype="extra2"/>
+ </vbox>
+ </hbox>
+</dialog>
+</html:body>
+</html>
diff --git a/comm/mailnews/search/content/FilterEditor.js b/comm/mailnews/search/content/FilterEditor.js
new file mode 100644
index 0000000000..3b93773192
--- /dev/null
+++ b/comm/mailnews/search/content/FilterEditor.js
@@ -0,0 +1,809 @@
+/* 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/. */
+
+/* import-globals-from searchTerm.js */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm");
+var { PluralForm } = ChromeUtils.importESModule(
+ "resource://gre/modules/PluralForm.sys.mjs"
+);
+
+// The actual filter that we're editing if it is a _saved_ filter or prefill;
+// void otherwise.
+var gFilter;
+// cache the key elements we need
+var gFilterList;
+// The filter name as it appears in the "Filter Name" field of dialog.
+var gFilterNameElement;
+var gFilterTypeSelector;
+var gFilterBundle;
+var gPreFillName;
+var gFilterActionList;
+var gCustomActions = null;
+var gFilterType;
+var gFilterPosition = 0;
+
+var gFilterActionStrings = [
+ "none",
+ "movemessage",
+ "setpriorityto",
+ "deletemessage",
+ "markasread",
+ "ignorethread",
+ "watchthread",
+ "markasflagged",
+ "label",
+ "replytomessage",
+ "forwardmessage",
+ "stopexecution",
+ "deletefrompopserver",
+ "leaveonpopserver",
+ "setjunkscore",
+ "fetchfrompopserver",
+ "copymessage",
+ "addtagtomessage",
+ "ignoresubthread",
+ "markasunread",
+];
+
+// A temporary filter with the current state of actions in the UI.
+var gTempFilter = null;
+// nsIMsgRuleAction[] - the currently defined actions in the order they will be run.
+var gActionListOrdered = null;
+
+var gFilterEditorMsgWindow = null;
+
+window.addEventListener("DOMContentLoaded", filterEditorOnLoad, { once: true });
+document.addEventListener("dialogaccept", onAccept);
+
+function filterEditorOnLoad() {
+ getCustomActions();
+ initializeSearchWidgets();
+ initializeFilterWidgets();
+
+ gFilterBundle = document.getElementById("bundle_filter");
+
+ if ("arguments" in window && window.arguments[0]) {
+ var args = window.arguments[0];
+
+ if ("filterList" in args) {
+ gFilterList = args.filterList;
+ // the postPlugin filters cannot be applied to servers that are
+ // deferred, (you must define them on the deferredTo server instead).
+ let server = gFilterList.folder.server;
+ if (server.rootFolder != server.rootMsgFolder) {
+ gFilterTypeSelector.disableDeferredAccount();
+ }
+ }
+
+ if ("filterPosition" in args) {
+ gFilterPosition = args.filterPosition;
+ }
+
+ if ("filter" in args) {
+ // editing a filter
+ gFilter = window.arguments[0].filter;
+ initializeDialog(gFilter);
+ } else {
+ if (gFilterList) {
+ setSearchScope(getScopeFromFilterList(gFilterList));
+ }
+ // if doing prefill filter create a new filter and populate it.
+ if ("filterName" in args) {
+ gPreFillName = args.filterName;
+
+ // Passing null as the parameter to createFilter to keep the name empty
+ // until later where we assign the name.
+ gFilter = gFilterList.createFilter(null);
+
+ var term = gFilter.createTerm();
+
+ term.attrib = Ci.nsMsgSearchAttrib.Default;
+ if ("fieldName" in args && args.fieldName) {
+ // fieldName should contain the name of the field in which to search,
+ // from nsMsgSearchTerm.cpp::SearchAttribEntryTable, e.g. "to" or "cc"
+ try {
+ term.attrib = term.getAttributeFromString(args.fieldName);
+ } catch (e) {
+ /* Invalid string is fine, just ignore it. */
+ }
+ }
+ if (term.attrib == Ci.nsMsgSearchAttrib.Default) {
+ term.attrib = Ci.nsMsgSearchAttrib.Sender;
+ }
+
+ term.op = Ci.nsMsgSearchOp.Is;
+ term.booleanAnd = gSearchBooleanRadiogroup.value == "and";
+
+ var termValue = term.value;
+ termValue.attrib = term.attrib;
+ termValue.str = gPreFillName;
+
+ term.value = termValue;
+
+ gFilter.appendTerm(term);
+
+ // the default action for news filters is Delete
+ // for everything else, it's MoveToFolder
+ var filterAction = gFilter.createAction();
+ filterAction.type =
+ getScopeFromFilterList(gFilterList) == Ci.nsMsgSearchScope.newsFilter
+ ? Ci.nsMsgFilterAction.Delete
+ : Ci.nsMsgFilterAction.MoveToFolder;
+ gFilter.appendAction(filterAction);
+ initializeDialog(gFilter);
+ } else if ("copiedFilter" in args) {
+ // we are copying a filter
+ let copiedFilter = args.copiedFilter;
+ let copiedName = gFilterBundle.getFormattedString(
+ "copyToNewFilterName",
+ [copiedFilter.filterName]
+ );
+ let newFilter = gFilterList.createFilter(copiedName);
+
+ // copy the actions
+ for (let i = 0; i < copiedFilter.actionCount; i++) {
+ let filterAction = copiedFilter.getActionAt(i);
+ newFilter.appendAction(filterAction);
+ }
+
+ // copy the search terms
+ for (let searchTerm of copiedFilter.searchTerms) {
+ let newTerm = newFilter.createTerm();
+ newTerm.attrib = searchTerm.attrib;
+ newTerm.op = searchTerm.op;
+ newTerm.booleanAnd = searchTerm.booleanAnd;
+ newTerm.value = searchTerm.value;
+ newFilter.appendTerm(newTerm);
+ }
+
+ newFilter.filterType = copiedFilter.filterType;
+
+ gPreFillName = copiedName;
+ gFilter = newFilter;
+
+ initializeDialog(gFilter);
+
+ // We reset the filter name, because otherwise the saveFilter()
+ // function thinks we are editing a filter, and will thus skip the name
+ // uniqueness check.
+ gFilter.filterName = "";
+ } else {
+ // fake the first more button press
+ onMore(null);
+ }
+ }
+ }
+
+ if (!gFilter) {
+ // This is a new filter. Set to both Incoming and Manual contexts.
+ gFilterTypeSelector.setType(
+ Ci.nsMsgFilterType.Incoming | Ci.nsMsgFilterType.Manual
+ );
+ }
+
+ // in the case of a new filter, we may not have an action row yet.
+ ensureActionRow();
+ gFilterType = gFilterTypeSelector.getType();
+
+ gFilterNameElement.select();
+ // This call is required on mac and linux. It has no effect under win32. See bug 94800.
+ gFilterNameElement.focus();
+}
+
+function onEnterInSearchTerm(event) {
+ if (event.ctrlKey || (Services.appinfo.OS == "Darwin" && event.metaKey)) {
+ // If accel key (Ctrl on Win/Linux, Cmd on Mac) was held too, accept the dialog.
+ document.querySelector("dialog").acceptDialog();
+ } else {
+ // If only plain Enter was pressed, add a new rule line.
+ onMore(event);
+ }
+}
+
+function onAccept(event) {
+ try {
+ if (!saveFilter()) {
+ event.preventDefault();
+ return;
+ }
+ } catch (e) {
+ console.error(e);
+ event.preventDefault();
+ return;
+ }
+
+ // parent should refresh filter list..
+ // this should REALLY only happen when some criteria changes that
+ // are displayed in the filter dialog, like the filter name
+ window.arguments[0].refresh = true;
+ window.arguments[0].newFilter = gFilter;
+}
+
+function duplicateFilterNameExists(filterName) {
+ if (gFilterList) {
+ for (var i = 0; i < gFilterList.filterCount; i++) {
+ if (filterName == gFilterList.getFilterAt(i).filterName) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+function getScopeFromFilterList(filterList) {
+ if (!filterList) {
+ dump("yikes, null filterList\n");
+ return Ci.nsMsgSearchScope.offlineMail;
+ }
+ return filterList.folder.server.filterScope;
+}
+
+function getScope(filter) {
+ return getScopeFromFilterList(filter.filterList);
+}
+
+function initializeFilterWidgets() {
+ gFilterNameElement = document.getElementById("filterName");
+ gFilterActionList = document.getElementById("filterActionList");
+ initializeFilterTypeSelector();
+}
+
+function initializeFilterTypeSelector() {
+ /**
+ * This object controls code interaction with the widget allowing specifying
+ * the filter type (event when the filter is run).
+ */
+ gFilterTypeSelector = {
+ checkBoxManual: document.getElementById("runManual"),
+ checkBoxIncoming: document.getElementById("runIncoming"),
+
+ menulistIncoming: document.getElementById("pluginsRunOrder"),
+
+ menuitemBeforePlugins: document.getElementById("runBeforePlugins"),
+ menuitemAfterPlugins: document.getElementById("runAfterPlugins"),
+
+ checkBoxArchive: document.getElementById("runArchive"),
+ checkBoxOutgoing: document.getElementById("runOutgoing"),
+ checkBoxPeriodic: document.getElementById("runPeriodic"),
+
+ /**
+ * Returns the currently set filter type (checkboxes) in terms
+ * of a Ci.Ci.nsMsgFilterType value.
+ */
+ getType() {
+ let type = Ci.nsMsgFilterType.None;
+
+ if (this.checkBoxManual.checked) {
+ type |= Ci.nsMsgFilterType.Manual;
+ }
+
+ if (this.checkBoxIncoming.checked) {
+ if (this.menulistIncoming.selectedItem == this.menuitemAfterPlugins) {
+ type |= Ci.nsMsgFilterType.PostPlugin;
+ } else if (
+ getScopeFromFilterList(gFilterList) == Ci.nsMsgSearchScope.newsFilter
+ ) {
+ type |= Ci.nsMsgFilterType.NewsRule;
+ } else {
+ type |= Ci.nsMsgFilterType.InboxRule;
+ }
+ }
+
+ if (this.checkBoxArchive.checked) {
+ type |= Ci.nsMsgFilterType.Archive;
+ }
+
+ if (this.checkBoxOutgoing.checked) {
+ type |= Ci.nsMsgFilterType.PostOutgoing;
+ }
+
+ if (this.checkBoxPeriodic.checked) {
+ type |= Ci.nsMsgFilterType.Periodic;
+ }
+
+ return type;
+ },
+
+ /**
+ * Sets the checkboxes to represent the filter type passed in.
+ *
+ * @param aType the filter type to set in terms
+ * of Ci.Ci.nsMsgFilterType values.
+ */
+ setType(aType) {
+ // If there is no type (event) requested, force "when manually run"
+ if (aType == Ci.nsMsgFilterType.None) {
+ aType = Ci.nsMsgFilterType.Manual;
+ }
+
+ this.checkBoxManual.checked = aType & Ci.nsMsgFilterType.Manual;
+
+ this.checkBoxIncoming.checked =
+ aType & (Ci.nsMsgFilterType.PostPlugin | Ci.nsMsgFilterType.Incoming);
+
+ this.menulistIncoming.selectedItem =
+ aType & Ci.nsMsgFilterType.PostPlugin
+ ? this.menuitemAfterPlugins
+ : this.menuitemBeforePlugins;
+
+ this.checkBoxArchive.checked = aType & Ci.nsMsgFilterType.Archive;
+
+ this.checkBoxOutgoing.checked = aType & Ci.nsMsgFilterType.PostOutgoing;
+
+ this.checkBoxPeriodic.checked = aType & Ci.nsMsgFilterType.Periodic;
+ const periodMinutes = gFilterList.folder.server.getIntValue(
+ "periodicFilterRateMinutes"
+ );
+ document.getElementById("runPeriodic").label = PluralForm.get(
+ periodMinutes,
+ gFilterBundle.getString("contextPeriodic.label")
+ ).replace("#1", periodMinutes);
+
+ this.updateClassificationMenu();
+ },
+
+ /**
+ * Enable the "before/after classification" menulist depending on
+ * whether "run when incoming mail" is selected.
+ */
+ updateClassificationMenu() {
+ this.menulistIncoming.disabled = !this.checkBoxIncoming.checked;
+ updateFilterType();
+ },
+
+ /**
+ * Disable the options unsuitable for deferred accounts.
+ */
+ disableDeferredAccount() {
+ this.menuitemAfterPlugins.disabled = true;
+ this.checkBoxOutgoing.disabled = true;
+ },
+ };
+}
+
+function initializeDialog(filter) {
+ gFilterNameElement.value = filter.filterName;
+ gFilterTypeSelector.setType(filter.filterType);
+
+ let numActions = filter.actionCount;
+ for (let actionIndex = 0; actionIndex < numActions; actionIndex++) {
+ let filterAction = filter.getActionAt(actionIndex);
+
+ let newActionRow = document.createXULElement("richlistitem", {
+ is: "ruleaction-richlistitem",
+ });
+ newActionRow.setAttribute("initialActionIndex", actionIndex);
+ newActionRow.className = "ruleaction";
+ gFilterActionList.appendChild(newActionRow);
+ newActionRow.setAttribute(
+ "value",
+ filterAction.type == Ci.nsMsgFilterAction.Custom
+ ? filterAction.customId
+ : gFilterActionStrings[filterAction.type]
+ );
+ newActionRow.setAttribute("onfocus", "this.storeFocus();");
+ }
+
+ var gSearchScope = getFilterScope(
+ getScope(filter),
+ filter.filterType,
+ filter.filterList
+ );
+ initializeSearchRows(gSearchScope, filter.searchTerms);
+ setFilterScope(filter.filterType, filter.filterList);
+}
+
+function ensureActionRow() {
+ // make sure we have at least one action row visible to the user
+ if (!gFilterActionList.getRowCount()) {
+ let newActionRow = document.createXULElement("richlistitem", {
+ is: "ruleaction-richlistitem",
+ });
+ newActionRow.className = "ruleaction";
+ gFilterActionList.appendChild(newActionRow);
+ newActionRow.mRemoveButton.disabled = true;
+ }
+}
+
+// move to overlay
+function saveFilter() {
+ // See if at least one filter type (activation event) is selected.
+ if (gFilterType == Ci.nsMsgFilterType.None) {
+ Services.prompt.alert(
+ window,
+ gFilterBundle.getString("mustHaveFilterTypeTitle"),
+ gFilterBundle.getString("mustHaveFilterTypeMessage")
+ );
+ return false;
+ }
+
+ let filterName = gFilterNameElement.value;
+ // If we think have a duplicate, then we need to check that if we
+ // have an original filter name (i.e. we are editing a filter), then
+ // we must check that the original is not the current as that is what
+ // the duplicateFilterNameExists function will have picked up.
+ if (
+ (!gFilter || gFilter.filterName != filterName) &&
+ duplicateFilterNameExists(filterName)
+ ) {
+ Services.prompt.alert(
+ window,
+ gFilterBundle.getString("cannotHaveDuplicateFilterTitle"),
+ gFilterBundle.getString("cannotHaveDuplicateFilterMessage")
+ );
+ return false;
+ }
+
+ // Check that all of the search attributes and operators are valid.
+ function rule_desc(index, obj) {
+ return (
+ index +
+ 1 +
+ " (" +
+ obj.searchattribute.label +
+ ", " +
+ obj.searchoperator.label +
+ ")"
+ );
+ }
+
+ let invalidRule = false;
+ for (let index = 0; index < gSearchTerms.length; index++) {
+ let obj = gSearchTerms[index].obj;
+ // We don't need to check validity of matchAll terms
+ if (obj.matchAll) {
+ continue;
+ }
+
+ // the term might be an offscreen one that we haven't initialized yet
+ let searchTerm = obj.searchTerm;
+ if (!searchTerm && !gSearchTerms[index].initialized) {
+ continue;
+ }
+
+ if (isNaN(obj.searchattribute.value)) {
+ // is this a custom term?
+ let customTerm = MailServices.filters.getCustomTerm(
+ obj.searchattribute.value
+ );
+ if (!customTerm) {
+ invalidRule = true;
+ console.error(
+ "Filter not saved because custom search term '" +
+ obj.searchattribute.value +
+ "' in rule " +
+ rule_desc(index, obj) +
+ " not found"
+ );
+ } else if (
+ !customTerm.getAvailable(obj.searchScope, obj.searchattribute.value)
+ ) {
+ invalidRule = true;
+ console.error(
+ "Filter not saved because custom search term '" +
+ customTerm.name +
+ "' in rule " +
+ rule_desc(index, obj) +
+ " not available"
+ );
+ }
+ } else {
+ let otherHeader = Ci.nsMsgSearchAttrib.OtherHeader;
+ let attribValue =
+ obj.searchattribute.value > otherHeader
+ ? otherHeader
+ : obj.searchattribute.value;
+ if (
+ !obj.searchattribute.validityTable.getAvailable(
+ attribValue,
+ obj.searchoperator.value
+ )
+ ) {
+ invalidRule = true;
+ console.error(
+ "Filter not saved because standard search term '" +
+ attribValue +
+ "' in rule " +
+ rule_desc(index, obj) +
+ " not available in this context"
+ );
+ }
+ }
+
+ if (invalidRule) {
+ Services.prompt.alert(
+ window,
+ gFilterBundle.getString("searchTermsInvalidTitle"),
+ gFilterBundle.getFormattedString("searchTermsInvalidRule", [
+ obj.searchattribute.label,
+ obj.searchoperator.label,
+ ])
+ );
+ return false;
+ }
+ }
+
+ // before we go any further, validate each specified filter action, abort the save
+ // if any of the actions is invalid...
+ for (let index = 0; index < gFilterActionList.itemCount; index++) {
+ var listItem = gFilterActionList.getItemAtIndex(index);
+ if (!listItem.validateAction()) {
+ return false;
+ }
+ }
+
+ // if we made it here, all of the actions are valid, so go ahead and save the filter
+ let isNewFilter;
+ if (!gFilter) {
+ // This is a new filter
+ gFilter = gFilterList.createFilter(filterName);
+ isNewFilter = true;
+ gFilter.enabled = true;
+ } else {
+ // We are working with an existing filter object,
+ // either editing or using prefill
+ gFilter.filterName = filterName;
+ // Prefilter is treated as a new filter.
+ if (gPreFillName) {
+ isNewFilter = true;
+ gFilter.enabled = true;
+ } else {
+ isNewFilter = false;
+ }
+
+ gFilter.clearActionList();
+ }
+
+ // add each filteraction to the filter
+ for (let index = 0; index < gFilterActionList.itemCount; index++) {
+ gFilterActionList.getItemAtIndex(index).saveToFilter(gFilter);
+ }
+
+ // If we do not have a filter name at this point, generate one.
+ if (!gFilter.filterName) {
+ AssignMeaningfulName();
+ }
+
+ gFilter.filterType = gFilterType;
+ gFilter.searchTerms = saveSearchTerms(gFilter.searchTerms, gFilter);
+
+ if (isNewFilter) {
+ // new filter - insert into gFilterList
+ gFilterList.insertFilterAt(gFilterPosition, gFilter);
+ }
+
+ // success!
+ return true;
+}
+
+/**
+ * Check if the list of actions the user created will be executed in a different order.
+ * Exposes a note to the user if that is the case.
+ */
+function checkActionsReorder() {
+ setTimeout(_checkActionsReorder, 0);
+}
+
+/**
+ * This should be called from setTimeout otherwise some of the elements calling
+ * may not be fully initialized yet (e.g. we get ".saveToFilter is not a function").
+ * It is OK to schedule multiple timeouts with this function.
+ */
+function _checkActionsReorder() {
+ // Create a temporary disposable filter and add current actions to it.
+ if (!gTempFilter) {
+ gTempFilter = gFilterList.createFilter("");
+ } else {
+ gTempFilter.clearActionList();
+ }
+
+ for (let index = 0; index < gFilterActionList.itemCount; index++) {
+ gFilterActionList.getItemAtIndex(index).saveToFilter(gTempFilter);
+ }
+
+ // Now get the actions out of the filter in the order they will be executed in.
+ gActionListOrdered = gTempFilter.sortedActionList;
+
+ // Compare the two lists.
+ let statusBar = document.getElementById("statusbar");
+ for (let index = 0; index < gActionListOrdered.length; index++) {
+ if (index != gTempFilter.getActionIndex(gActionListOrdered[index])) {
+ // If the lists are not the same unhide the status bar and show warning.
+ statusBar.style.visibility = "visible";
+ return;
+ }
+ }
+
+ statusBar.style.visibility = "hidden";
+}
+
+/**
+ * Show a dialog with the ordered list of actions.
+ * The fetching of action label and argument is separated from checkActionsReorder
+ * function to make that one more lightweight. The list is built only upon
+ * user request.
+ */
+function showActionsOrder() {
+ // Fetch the actions and arguments as a string.
+ let actionStrings = [];
+ for (let i = 0; i < gFilterActionList.itemCount; i++) {
+ let ruleAction = gFilterActionList.getItemAtIndex(i);
+ let actionTarget = ruleAction.children[1];
+ let actionItem = actionTarget.ruleactiontargetElement;
+ let actionItemLabel = actionItem && actionItem.children[0].label;
+
+ let actionString = {
+ label: ruleAction.mRuleActionType.label,
+ argument: "",
+ };
+ if (actionItem) {
+ if (actionItemLabel) {
+ actionString.argument = actionItemLabel;
+ } else {
+ actionString.argument = actionItem.children[0].value;
+ }
+ }
+ actionStrings.push(actionString);
+ }
+
+ // Present a nicely formatted list of action names and arguments.
+ let actionList = gFilterBundle.getString("filterActionOrderExplanation");
+ for (let i = 0; i < gActionListOrdered.length; i++) {
+ let actionIndex = gTempFilter.getActionIndex(gActionListOrdered[i]);
+ let action = actionStrings[actionIndex];
+ actionList += gFilterBundle.getFormattedString("filterActionItem", [
+ i + 1,
+ action.label,
+ action.argument,
+ ]);
+ }
+
+ Services.prompt.confirmEx(
+ window,
+ gFilterBundle.getString("filterActionOrderTitle"),
+ actionList,
+ Services.prompt.BUTTON_TITLE_OK,
+ null,
+ null,
+ null,
+ null,
+ { value: false }
+ );
+}
+
+function AssignMeaningfulName() {
+ // termRoot points to the first search object, which is the one we care about.
+ let termRoot = gSearchTerms[0].obj;
+ // stub is used as the base name for a filter.
+ let stub;
+
+ // If this is a Match All Messages Filter, we already know the name to assign.
+ if (termRoot.matchAll) {
+ stub = gFilterBundle.getString("matchAllFilterName");
+ } else {
+ // Assign a name based on the first search term.
+ let term = termRoot.searchattribute.label;
+ let operator = termRoot.searchoperator.label;
+ let value = termRoot.searchvalue.getReadableValue();
+ stub = gFilterBundle.getFormattedString("filterAutoNameStr", [
+ term,
+ operator,
+ value,
+ ]);
+ }
+
+ // Whatever name we have used, 'uniquify' it.
+ let tempName = stub;
+ let count = 1;
+ while (duplicateFilterNameExists(tempName)) {
+ count++;
+ tempName = `${stub} ${count}`;
+ }
+ gFilter.filterName = tempName;
+}
+
+function UpdateAfterCustomHeaderChange() {
+ updateSearchAttributes();
+}
+
+// if you use msgWindow, please make sure that destructor gets called when you close the "window"
+function GetFilterEditorMsgWindow() {
+ if (!gFilterEditorMsgWindow) {
+ var msgWindowContractID = "@mozilla.org/messenger/msgwindow;1";
+ var nsIMsgWindow = Ci.nsIMsgWindow;
+ gFilterEditorMsgWindow =
+ Cc[msgWindowContractID].createInstance(nsIMsgWindow);
+ gFilterEditorMsgWindow.domWindow = window;
+ gFilterEditorMsgWindow.rootDocShell.appType = Ci.nsIDocShell.APP_TYPE_MAIL;
+ }
+ return gFilterEditorMsgWindow;
+}
+
+function SetBusyCursor(window, enable) {
+ // setCursor() is only available for chrome windows.
+ // However one of our frames is the start page which
+ // is a non-chrome window, so check if this window has a
+ // setCursor method
+ if ("setCursor" in window) {
+ if (enable) {
+ window.setCursor("wait");
+ } else {
+ window.setCursor("auto");
+ }
+ }
+}
+
+/* globals openHelp */
+// suite/components/helpviewer/content/contextHelp.js
+function doHelpButton() {
+ openHelp("mail-filters");
+}
+
+function getCustomActions() {
+ if (!gCustomActions) {
+ gCustomActions = MailServices.filters.getCustomActions();
+ }
+}
+
+function updateFilterType() {
+ gFilterType = gFilterTypeSelector.getType();
+ setFilterScope(gFilterType, gFilterList);
+
+ // set valid actions
+ var ruleActions = gFilterActionList.getElementsByAttribute(
+ "class",
+ "ruleaction"
+ );
+ for (var i = 0; i < ruleActions.length; i++) {
+ ruleActions[i].mRuleActionType.hideInvalidActions();
+ }
+}
+
+// Given a filter type, set the global search scope to the filter scope
+function setFilterScope(aFilterType, aFilterList) {
+ let filterScope = getFilterScope(
+ getScopeFromFilterList(aFilterList),
+ aFilterType,
+ aFilterList
+ );
+ setSearchScope(filterScope);
+}
+
+//
+// Given the base filter scope for a server, and the filter
+// type, return the scope used for filter. This assumes a
+// hierarchy of contexts, with incoming the most restrictive,
+// followed by manual and post-plugin.
+function getFilterScope(aServerFilterScope, aFilterType, aFilterList) {
+ if (aFilterType & Ci.nsMsgFilterType.Incoming) {
+ return aServerFilterScope;
+ }
+
+ // Manual or PostPlugin
+ // local mail allows body and junk types
+ if (aServerFilterScope == Ci.nsMsgSearchScope.offlineMailFilter) {
+ return Ci.nsMsgSearchScope.offlineMail;
+ }
+ // IMAP and NEWS online don't allow body
+ return Ci.nsMsgSearchScope.onlineManual;
+}
+
+/**
+ * Re-focus the action that was focused before focus was lost.
+ */
+function setLastActionFocus() {
+ let lastAction = gFilterActionList.getAttribute("focusedAction");
+ if (!lastAction || lastAction < 0) {
+ lastAction = 0;
+ }
+ if (lastAction >= gFilterActionList.itemCount) {
+ lastAction = gFilterActionList.itemCount - 1;
+ }
+
+ gFilterActionList.getItemAtIndex(lastAction).mRuleActionType.focus();
+}
diff --git a/comm/mailnews/search/content/FilterEditor.xhtml b/comm/mailnews/search/content/FilterEditor.xhtml
new file mode 100644
index 0000000000..00188392bc
--- /dev/null
+++ b/comm/mailnews/search/content/FilterEditor.xhtml
@@ -0,0 +1,136 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderPane.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/icons.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/contextMenu.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/input-fields.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/themeableDialog.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/filterDialog.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/filterEditor.css" type="text/css"?>
+
+<!DOCTYPE html [
+ <!ENTITY % filterEditorDTD SYSTEM "chrome://messenger/locale/FilterEditor.dtd">
+ %filterEditorDTD;
+ <!ENTITY % searchTermDTD SYSTEM "chrome://messenger/locale/searchTermOverlay.dtd">
+ %searchTermDTD;
+]>
+<html id="FilterEditor" xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ windowtype="mailnews:filtereditor"
+ lightweightthemes="true"
+ style="min-width: 900px; min-height: 600px;"
+ scrolling="false">
+<head>
+ <title>&window.title;</title>
+ <script defer="defer" src="chrome://messenger/content/globalOverlay.js"></script>
+ <script defer="defer" src="chrome://global/content/editMenuOverlay.js"></script>
+ <script defer="defer" src="chrome://messenger/content/searchWidgets.js"></script>
+ <script defer="defer" src="chrome://messenger/content/mailWindowOverlay.js"></script>
+ <script defer="defer" src="chrome://messenger/content/mailCommands.js"></script>
+ <script defer="defer" src="chrome://messenger/content/searchTerm.js"></script>
+ <script defer="defer" src="chrome://messenger/content/dateFormat.js"></script>
+ <script defer="defer" src="chrome://messenger/content/dialogShadowDom.js"></script>
+ <script defer="defer" src="chrome://messenger/content/FilterEditor.js"></script>
+</head>
+<html:body xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<dialog buttons="accept,cancel">
+ <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
+ <stringbundle id="bundle_filter" src="chrome://messenger/locale/filter.properties"/>
+
+ <commandset>
+ <command id="cmd_updateFilterType" oncommand="updateFilterType();"/>
+ <command id="cmd_updateClassificationMenu" oncommand="gFilterTypeSelector.updateClassificationMenu();"/>
+ </commandset>
+
+ <html:div id="filterNameBox" class="input-container">
+ <label id="filterNameLabel"
+ value="&filterName.label;"
+ accesskey="&filterName.accesskey;"
+ control="filterName"/>
+ <html:input id="filterName"
+ type="text"
+ class="input-inline"
+ aria-labelledby="filterNameLabel"/>
+ </html:div>
+
+ <html:fieldset id="applyFiltersSettings">
+ <html:legend>&contextDesc.label;</html:legend>
+ <vbox>
+ <hbox flex="1" align="center">
+ <checkbox id="runManual"
+ label="&contextManual.label;"
+ accesskey="&contextManual.accesskey;"
+ command="cmd_updateFilterType"/>
+ </hbox>
+ <hbox flex="1" align="center">
+ <checkbox id="runIncoming"
+ label="&contextIncomingMail.label;"
+ accesskey="&contextIncomingMail.accesskey;"
+ command="cmd_updateClassificationMenu"/>
+ <menulist id="pluginsRunOrder"
+ command="cmd_updateFilterType">
+ <menupopup>
+ <menuitem id="runBeforePlugins"
+ label="&contextBeforeCls.label;"/>
+ <menuitem id="runAfterPlugins"
+ label="&contextAfterCls.label;"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ <hbox flex="1" align="center">
+ <checkbox id="runArchive"
+ label="&contextArchive.label;"
+ accesskey="&contextArchive.accesskey;"
+ command="cmd_updateFilterType"/>
+ </hbox>
+ <hbox flex="1" align="center">
+ <checkbox id="runOutgoing"
+ label="&contextOutgoing.label;"
+ accesskey="&contextOutgoing.accesskey;"
+ command="cmd_updateFilterType"/>
+ </hbox>
+ <hbox flex="1" align="center">
+ <checkbox id="runPeriodic"
+ accesskey="&contextPeriodic.accesskey;"
+ command="cmd_updateFilterType"/>
+ <label id="periodLength"/>
+ </hbox>
+ </vbox>
+ </html:fieldset>
+
+
+ <vbox id="searchTermListBox">
+#include searchTerm.inc.xhtml
+
+ <splitter id="gray_horizontal_splitter" persist="state" orient="vertical"/>
+
+ <vbox id="filterActionsBox">
+ <label value="&filterActionDesc.label;"
+ accesskey="&filterActionDesc.accesskey;"
+ control="filterActionList"/>
+ <richlistbox id="filterActionList"
+ flex="1"
+ style="min-height: 100px;"
+ onfocus="setLastActionFocus();"
+ focusedAction="0">
+ </richlistbox>
+ </vbox>
+
+ <vbox id="statusbar" style="visibility: hidden;">
+ <hbox align="center">
+ <label>
+ &filterActionOrderWarning.label;
+ </label>
+ <label id="seeExecutionOrder" class="text-link"
+ onclick="showActionsOrder();">&filterActionOrder.label;</label>
+ </hbox>
+ </vbox>
+</dialog>
+</html:body>
+</html>
diff --git a/comm/mailnews/search/content/searchTerm.inc.xhtml b/comm/mailnews/search/content/searchTerm.inc.xhtml
new file mode 100644
index 0000000000..07e3407a4c
--- /dev/null
+++ b/comm/mailnews/search/content/searchTerm.inc.xhtml
@@ -0,0 +1,27 @@
+# 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/.
+
+ <radiogroup id="booleanAndGroup" orient="horizontal" value="and"
+ oncommand="booleanChanged(event);">
+ <radio value="and" label="&matchAll.label;"
+ accesskey="&matchAll.accesskey;" flex="1"/>
+ <radio value="or" label="&matchAny.label;"
+ accesskey="&matchAny.accesskey;" flex="1"/>
+ <radio value="matchAll" id="matchAllItem" label="&matchAllMsgs.label;"
+ accesskey="&matchAllMsgs.accesskey;" flex="1"/>
+ </radiogroup>
+
+ <hbox id="searchTermBox" style="flex: 1 1 0;">
+ <hbox id="searchterms" class="themeable-brighttext"/>
+ <richlistbox id="searchTermList" flex="1">
+ <treecols hidden="true">
+ <treecol style="flex: &searchTermListAttributesFlexValue; auto"/>
+ <treecol style="flex: &searchTermListOperatorsFlexValue; auto"/>
+ <treecol style="flex: &searchTermListValueFlexValue; auto"/>
+ <treecol class="filler"/>
+ </treecols>
+ </richlistbox>
+
+ </hbox>
+ </vbox>
diff --git a/comm/mailnews/search/content/searchTerm.js b/comm/mailnews/search/content/searchTerm.js
new file mode 100644
index 0000000000..10d085a5e0
--- /dev/null
+++ b/comm/mailnews/search/content/searchTerm.js
@@ -0,0 +1,568 @@
+/* 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/. */
+
+// abSearchDialog.js
+/* globals GetScopeForDirectoryURI */
+
+var gTotalSearchTerms = 0;
+var gSearchTermList;
+var gSearchTerms = [];
+var gSearchRemovedTerms = [];
+var gSearchScope;
+var gSearchBooleanRadiogroup;
+
+var gUniqueSearchTermCounter = 0; // gets bumped every time we add a search term so we can always
+// dynamically generate unique IDs for the terms.
+
+// cache these so we don't have to hit the string bundle for them
+var gMoreButtonTooltipText;
+var gLessButtonTooltipText;
+var gLoading = true;
+
+function searchTermContainer() {}
+
+searchTermContainer.prototype = {
+ internalSearchTerm: "",
+ internalBooleanAnd: "",
+
+ // this.searchTerm: the actual nsIMsgSearchTerm object
+ get searchTerm() {
+ return this.internalSearchTerm;
+ },
+ set searchTerm(val) {
+ this.internalSearchTerm = val;
+
+ var term = val;
+ // val is a nsIMsgSearchTerm
+ var searchAttribute = this.searchattribute;
+ var searchOperator = this.searchoperator;
+ var searchValue = this.searchvalue;
+
+ // now reflect all attributes of the searchterm into the widgets
+ if (searchAttribute) {
+ // for custom, the value is the custom id, not the integer attribute
+ if (term.attrib == Ci.nsMsgSearchAttrib.Custom) {
+ searchAttribute.value = term.customId;
+ } else {
+ searchAttribute.value = term.attrib;
+ }
+ }
+ if (searchOperator) {
+ searchOperator.value = val.op;
+ }
+ if (searchValue) {
+ searchValue.value = term.value;
+ }
+
+ this.booleanAnd = val.booleanAnd;
+ this.matchAll = val.matchAll;
+ },
+
+ // searchscope - just forward to the searchattribute
+ get searchScope() {
+ if (this.searchattribute) {
+ return this.searchattribute.searchScope;
+ }
+ return undefined;
+ },
+ set searchScope(val) {
+ var searchAttribute = this.searchattribute;
+ if (searchAttribute) {
+ searchAttribute.searchScope = val;
+ }
+ },
+
+ saveId(element, slot) {
+ this[slot] = element.id;
+ },
+
+ getElement(slot) {
+ return document.getElementById(this[slot]);
+ },
+
+ // three well-defined properties:
+ // searchattribute, searchoperator, searchvalue
+ // the trick going on here is that we're storing the Element's Id,
+ // not the element itself, because the XBL object may change out
+ // from underneath us
+ get searchattribute() {
+ return this.getElement("internalSearchAttributeId");
+ },
+ set searchattribute(val) {
+ this.saveId(val, "internalSearchAttributeId");
+ },
+ get searchoperator() {
+ return this.getElement("internalSearchOperatorId");
+ },
+ set searchoperator(val) {
+ this.saveId(val, "internalSearchOperatorId");
+ },
+ get searchvalue() {
+ return this.getElement("internalSearchValueId");
+ },
+ set searchvalue(val) {
+ this.saveId(val, "internalSearchValueId");
+ },
+
+ booleanNodes: null,
+ get booleanAnd() {
+ return this.internalBooleanAnd;
+ },
+ set booleanAnd(val) {
+ this.internalBooleanAnd = val;
+ },
+
+ save() {
+ var searchTerm = this.searchTerm;
+ var nsMsgSearchAttrib = Ci.nsMsgSearchAttrib;
+
+ if (isNaN(this.searchattribute.value)) {
+ // is this a custom term?
+ searchTerm.attrib = nsMsgSearchAttrib.Custom;
+ searchTerm.customId = this.searchattribute.value;
+ } else {
+ searchTerm.attrib = this.searchattribute.value;
+ }
+
+ if (
+ this.searchattribute.value > nsMsgSearchAttrib.OtherHeader &&
+ this.searchattribute.value < nsMsgSearchAttrib.kNumMsgSearchAttributes
+ ) {
+ searchTerm.arbitraryHeader = this.searchattribute.label;
+ }
+ searchTerm.op = this.searchoperator.value;
+ if (this.searchvalue.value) {
+ this.searchvalue.save();
+ } else {
+ this.searchvalue.saveTo(searchTerm.value);
+ }
+ searchTerm.value = this.searchvalue.value;
+ searchTerm.booleanAnd = this.booleanAnd;
+ searchTerm.matchAll = this.matchAll;
+ },
+ // if you have a search term element with no search term
+ saveTo(searchTerm) {
+ this.internalSearchTerm = searchTerm;
+ this.save();
+ },
+};
+
+function initializeSearchWidgets() {
+ gSearchBooleanRadiogroup = document.getElementById("booleanAndGroup");
+ gSearchTermList = document.getElementById("searchTermList");
+
+ // initialize some strings
+ var bundle = Services.strings.createBundle(
+ "chrome://messenger/locale/search.properties"
+ );
+ gMoreButtonTooltipText = bundle.GetStringFromName("moreButtonTooltipText");
+ gLessButtonTooltipText = bundle.GetStringFromName("lessButtonTooltipText");
+}
+
+function initializeBooleanWidgets() {
+ var booleanAnd = true;
+ var matchAll = false;
+ // get the boolean value from the first term
+ var firstTerm = gSearchTerms[0].searchTerm;
+ if (firstTerm) {
+ // If there is a second term, it should actually define whether we're
+ // using 'and' or not. Note that our UI is not as rich as the
+ // underlying search model, so there's the potential to lose here when
+ // grouping is involved.
+ booleanAnd =
+ gSearchTerms.length > 1
+ ? gSearchTerms[1].searchTerm.booleanAnd
+ : firstTerm.booleanAnd;
+ matchAll = firstTerm.matchAll;
+ }
+ // target radio items have value="and" or value="or" or "all"
+ if (matchAll) {
+ gSearchBooleanRadiogroup.value = "matchAll";
+ } else if (booleanAnd) {
+ gSearchBooleanRadiogroup.value = "and";
+ } else {
+ gSearchBooleanRadiogroup.value = "or";
+ }
+ var searchTerms = document.getElementById("searchTermList");
+ if (searchTerms) {
+ updateSearchTermsListbox(matchAll);
+ }
+}
+
+function initializeSearchRows(scope, searchTerms) {
+ for (let i = 0; i < searchTerms.length; i++) {
+ let searchTerm = searchTerms[i];
+ createSearchRow(i, scope, searchTerm, false);
+ gTotalSearchTerms++;
+ }
+ initializeBooleanWidgets();
+ updateRemoveRowButton();
+}
+
+/**
+ * Enables/disables all the visible elements inside the search terms listbox.
+ *
+ * @param matchAllValue boolean value from the first search term
+ */
+function updateSearchTermsListbox(matchAllValue) {
+ var searchTerms = document.getElementById("searchTermList");
+ searchTerms.setAttribute("disabled", matchAllValue);
+ var searchAttributeList =
+ searchTerms.getElementsByTagName("search-attribute");
+ var searchOperatorList = searchTerms.getElementsByTagName("search-operator");
+ var searchValueList = searchTerms.getElementsByTagName("search-value");
+ for (let i = 0; i < searchAttributeList.length; i++) {
+ searchAttributeList[i].setAttribute("disabled", matchAllValue);
+ searchOperatorList[i].setAttribute("disabled", matchAllValue);
+ searchValueList[i].setAttribute("disabled", matchAllValue);
+ if (!matchAllValue) {
+ searchValueList[i].removeAttribute("disabled");
+ }
+ }
+ var moreOrLessButtonsList = searchTerms.getElementsByTagName("button");
+ for (let i = 0; i < moreOrLessButtonsList.length; i++) {
+ moreOrLessButtonsList[i].setAttribute("disabled", matchAllValue);
+ }
+ if (!matchAllValue) {
+ updateRemoveRowButton();
+ }
+}
+
+// enables/disables the less button for the first row of search terms.
+function updateRemoveRowButton() {
+ var firstListItem = gSearchTermList.getItemAtIndex(0);
+ if (firstListItem) {
+ firstListItem.lastElementChild.lastElementChild.setAttribute(
+ "disabled",
+ gTotalSearchTerms == 1
+ );
+ }
+}
+
+// Returns the actual list item row index in the list of search rows
+// that contains the passed in element id.
+function getSearchRowIndexForElement(aElement) {
+ var listItem = aElement;
+
+ while (listItem && listItem.localName != "richlistitem") {
+ listItem = listItem.parentNode;
+ }
+
+ return gSearchTermList.getIndexOfItem(listItem);
+}
+
+function onMore(event) {
+ // if we have an event, extract the list row index and use that as the row number
+ // for our insertion point. If there is no event, append to the end....
+ var rowIndex;
+
+ if (event) {
+ rowIndex = getSearchRowIndexForElement(event.target) + 1;
+ } else {
+ rowIndex = gSearchTermList.getRowCount();
+ }
+
+ createSearchRow(rowIndex, gSearchScope, null, event != null);
+ gTotalSearchTerms++;
+ updateRemoveRowButton();
+
+ // the user just added a term, so scroll to it
+ gSearchTermList.ensureIndexIsVisible(rowIndex);
+}
+
+function onLess(event) {
+ if (event && gTotalSearchTerms > 1) {
+ removeSearchRow(getSearchRowIndexForElement(event.target));
+ --gTotalSearchTerms;
+ }
+
+ updateRemoveRowButton();
+}
+
+// set scope on all visible searchattribute tags
+function setSearchScope(scope) {
+ gSearchScope = scope;
+ for (var i = 0; i < gSearchTerms.length; i++) {
+ // don't set element attributes if XBL hasn't loaded
+ if (!(gSearchTerms[i].obj.searchattribute.searchScope === undefined)) {
+ gSearchTerms[i].obj.searchattribute.searchScope = scope;
+ // act like the user "selected" this, see bug #202848
+ gSearchTerms[i].obj.searchattribute.onSelect(null /* no event */);
+ }
+ gSearchTerms[i].scope = scope;
+ }
+}
+
+function updateSearchAttributes() {
+ for (var i = 0; i < gSearchTerms.length; i++) {
+ gSearchTerms[i].obj.searchattribute.refreshList();
+ }
+}
+
+function booleanChanged(event) {
+ // when boolean changes, we have to update all the attributes on the search terms
+ var newBoolValue = event.target.getAttribute("value") == "and";
+ var matchAllValue = event.target.getAttribute("value") == "matchAll";
+ if (document.getElementById("abPopup")) {
+ var selectedAB = document.getElementById("abPopup").selectedItem.value;
+ setSearchScope(GetScopeForDirectoryURI(selectedAB));
+ }
+ for (var i = 0; i < gSearchTerms.length; i++) {
+ let searchTerm = gSearchTerms[i].obj;
+ // If term is not yet initialized in the UI, change the original object.
+ if (!searchTerm || !gSearchTerms[i].initialized) {
+ searchTerm = gSearchTerms[i].searchTerm;
+ }
+
+ searchTerm.booleanAnd = newBoolValue;
+ searchTerm.matchAll = matchAllValue;
+ }
+ var searchTerms = document.getElementById("searchTermList");
+ if (searchTerms) {
+ if (!matchAllValue && searchTerms.hidden && !gTotalSearchTerms) {
+ // Fake to get empty row.
+ onMore(null);
+ }
+ updateSearchTermsListbox(matchAllValue);
+ }
+}
+
+/**
+ * Create a new search row with all the needed elements.
+ *
+ * @param index index of the position in the menulist where to add the row
+ * @param scope a nsMsgSearchScope constant indicating scope of this search rule
+ * @param searchTerm nsIMsgSearchTerm object to hold the search term
+ * @param aUserAdded boolean indicating if the row addition was initiated by the user
+ * (e.g. via the '+' button)
+ */
+function createSearchRow(index, scope, searchTerm, aUserAdded) {
+ var searchAttr = document.createXULElement("search-attribute");
+ var searchOp = document.createXULElement("search-operator");
+ var searchVal = document.createXULElement("search-value");
+
+ var moreButton = document.createXULElement("button");
+ var lessButton = document.createXULElement("button");
+ moreButton.setAttribute("class", "small-button");
+ moreButton.setAttribute("oncommand", "onMore(event);");
+ moreButton.setAttribute("label", "+");
+ moreButton.setAttribute("tooltiptext", gMoreButtonTooltipText);
+ lessButton.setAttribute("class", "small-button");
+ lessButton.setAttribute("oncommand", "onLess(event);");
+ lessButton.setAttribute("label", "\u2212");
+ lessButton.setAttribute("tooltiptext", gLessButtonTooltipText);
+
+ // now set up ids:
+ searchAttr.id = "searchAttr" + gUniqueSearchTermCounter;
+ searchOp.id = "searchOp" + gUniqueSearchTermCounter;
+ searchVal.id = "searchVal" + gUniqueSearchTermCounter;
+
+ searchAttr.setAttribute("for", searchOp.id + "," + searchVal.id);
+ searchOp.setAttribute("opfor", searchVal.id);
+
+ var rowdata = [searchAttr, searchOp, searchVal, [moreButton, lessButton]];
+ var searchrow = constructRow(rowdata);
+ searchrow.id = "searchRow" + gUniqueSearchTermCounter;
+
+ var searchTermObj = new searchTermContainer();
+ searchTermObj.searchattribute = searchAttr;
+ searchTermObj.searchoperator = searchOp;
+ searchTermObj.searchvalue = searchVal;
+
+ // now insert the new search term into our list of terms
+ gSearchTerms.splice(index, 0, {
+ obj: searchTermObj,
+ scope,
+ searchTerm,
+ initialized: false,
+ });
+
+ var editFilter = window.gFilter || null;
+ var editMailView = window.gMailView || null;
+
+ if (
+ (!editFilter && !editMailView) ||
+ (editFilter && index == gTotalSearchTerms) ||
+ (editMailView && index == gTotalSearchTerms)
+ ) {
+ gLoading = false;
+ }
+
+ // index is index of new row
+ // gTotalSearchTerms has not been updated yet
+ if (gLoading || index == gTotalSearchTerms) {
+ gSearchTermList.appendChild(searchrow);
+ } else {
+ var currentItem = gSearchTermList.getItemAtIndex(index);
+ gSearchTermList.insertBefore(searchrow, currentItem);
+ }
+
+ // If this row was added by user action, focus the value field.
+ if (aUserAdded) {
+ document.commandDispatcher.advanceFocusIntoSubtree(searchVal);
+ searchrow.setAttribute("highlight", "true");
+ }
+
+ // bump our unique search term counter
+ gUniqueSearchTermCounter++;
+}
+
+function initializeTermFromId(id) {
+ initializeTermFromIndex(
+ getSearchRowIndexForElement(document.getElementById(id))
+ );
+}
+
+function initializeTermFromIndex(index) {
+ var searchTermObj = gSearchTerms[index].obj;
+
+ searchTermObj.searchScope = gSearchTerms[index].scope;
+ // the search term will initialize the searchTerm element, including
+ // .booleanAnd
+ if (gSearchTerms[index].searchTerm) {
+ searchTermObj.searchTerm = gSearchTerms[index].searchTerm;
+ // here, we don't have a searchTerm, so it's probably a new element -
+ // we'll initialize the .booleanAnd from the existing setting in
+ // the UI
+ } else {
+ searchTermObj.booleanAnd = gSearchBooleanRadiogroup.value == "and";
+ if (index) {
+ // If we weren't pre-initialized with a searchTerm then steal the
+ // search attribute and operator from the previous row.
+ searchTermObj.searchattribute.value =
+ gSearchTerms[index - 1].obj.searchattribute.value;
+ searchTermObj.searchoperator.value =
+ gSearchTerms[index - 1].obj.searchoperator.value;
+ }
+ }
+
+ gSearchTerms[index].initialized = true;
+}
+
+/**
+ * Creates a <richlistitem> using the array children as the children
+ * of each listcell.
+ *
+ * @param aChildren An array of XUL elements to put into the listitem.
+ * Each array member is put into a separate listcell.
+ * If the member itself is an array of elements,
+ * all of them are put into the same listcell.
+ */
+function constructRow(aChildren) {
+ let cols = gSearchTermList.firstElementChild.children; // treecol elements
+ let listitem = document.createXULElement("richlistitem");
+ listitem.setAttribute("allowevents", "true");
+ for (let i = 0; i < aChildren.length; i++) {
+ let listcell = document.createXULElement("hbox");
+ if (cols[i].hasAttribute("style")) {
+ listcell.setAttribute("style", cols[i].getAttribute("style"));
+ }
+ let child = aChildren[i];
+
+ if (child instanceof Array) {
+ for (let j = 0; j < child.length; j++) {
+ listcell.appendChild(child[j]);
+ }
+ } else {
+ child.setAttribute("flex", "1");
+ listcell.appendChild(child);
+ }
+ listitem.appendChild(listcell);
+ }
+ return listitem;
+}
+
+function removeSearchRow(index) {
+ var searchTermObj = gSearchTerms[index].obj;
+ if (!searchTermObj) {
+ return;
+ }
+
+ // if it is an existing (but offscreen) term,
+ // make sure it is initialized before we remove it.
+ if (!gSearchTerms[index].searchTerm && !gSearchTerms[index].initialized) {
+ initializeTermFromIndex(index);
+ }
+
+ // need to remove row from list, so walk upwards from the
+ // searchattribute to find the first <listitem>
+ var listitem = searchTermObj.searchattribute;
+
+ while (listitem) {
+ if (listitem.localName == "richlistitem") {
+ break;
+ }
+ listitem = listitem.parentNode;
+ }
+
+ if (!listitem) {
+ dump("Error: couldn't find parent listitem!\n");
+ return;
+ }
+
+ if (searchTermObj.searchTerm) {
+ gSearchRemovedTerms.push(searchTermObj.searchTerm);
+ } else {
+ // dump("That wasn't real. ignoring \n");
+ }
+
+ listitem.remove();
+
+ // now remove the item from our list of terms
+ gSearchTerms.splice(index, 1);
+}
+
+/**
+ * Save the search terms from the UI back to the actual search terms.
+ *
+ * @param {nsIMsgSearchTerm[]} searchTerms - Array of terms
+ * @param {object} termOwner - Object which can contain and create the terms
+ * e.g. a nsIMsgSearchSession (will be unnecessary if we just make terms
+ * creatable via XPCOM).
+ * @returns {nsIMsgSearchTerm[]} The filtered searchTerms.
+ */
+function saveSearchTerms(searchTerms, termOwner) {
+ var matchAll = gSearchBooleanRadiogroup.value == "matchAll";
+ var i;
+
+ searchTerms = searchTerms.filter(t => !gSearchRemovedTerms.includes(t));
+
+ for (i = 0; i < gSearchTerms.length; i++) {
+ try {
+ gSearchTerms[i].obj.matchAll = matchAll;
+ var searchTerm = gSearchTerms[i].obj.searchTerm;
+ if (searchTerm) {
+ gSearchTerms[i].obj.save();
+ } else if (!gSearchTerms[i].initialized) {
+ // the term might be an offscreen one we haven't initialized yet
+ searchTerm = gSearchTerms[i].searchTerm;
+ } else {
+ // need to create a new searchTerm, and somehow save it to that
+ searchTerm = termOwner.createTerm();
+ gSearchTerms[i].obj.saveTo(searchTerm);
+ // this might not be the right place for the term,
+ // but we need to make the array longer anyway
+ termOwner.appendTerm(searchTerm);
+ }
+ searchTerms[i] = searchTerm;
+ } catch (ex) {
+ dump("** Error saving element " + i + ": " + ex + "\n");
+ }
+ }
+ return searchTerms;
+}
+
+function onReset(event) {
+ while (gTotalSearchTerms > 0) {
+ removeSearchRow(--gTotalSearchTerms);
+ }
+ onMore(null);
+}
+
+function hideMatchAllItem() {
+ var allItems = document.getElementById("matchAllItem");
+ if (allItems) {
+ allItems.hidden = true;
+ }
+}
diff --git a/comm/mailnews/search/content/searchWidgets.js b/comm/mailnews/search/content/searchWidgets.js
new file mode 100644
index 0000000000..add3ed29b8
--- /dev/null
+++ b/comm/mailnews/search/content/searchWidgets.js
@@ -0,0 +1,1779 @@
+/**
+ * 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/. */
+
+/* global MozElements MozXULElement */
+
+/* import-globals-from ../../base/content/dateFormat.js */
+// TODO: This is completely bogus. Only one use of this file also has FilterEditor.js.
+/* import-globals-from FilterEditor.js */
+
+// Wrap in a block to prevent leaking to window scope.
+{
+ const { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+ );
+ const { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm");
+
+ const updateParentNode = parentNode => {
+ if (parentNode.hasAttribute("initialActionIndex")) {
+ let actionIndex = parentNode.getAttribute("initialActionIndex");
+ let filterAction = gFilter.getActionAt(actionIndex);
+ parentNode.initWithAction(filterAction);
+ }
+ parentNode.updateRemoveButton();
+ };
+
+ class MozRuleactiontargetTag extends MozXULElement {
+ connectedCallback() {
+ const menulist = document.createXULElement("menulist");
+ const menuPopup = document.createXULElement("menupopup");
+
+ menulist.classList.add("ruleactionitem");
+ menulist.setAttribute("flex", "1");
+ menulist.appendChild(menuPopup);
+
+ for (let taginfo of MailServices.tags.getAllTags()) {
+ const newMenuItem = document.createXULElement("menuitem");
+ newMenuItem.setAttribute("label", taginfo.tag);
+ newMenuItem.setAttribute("value", taginfo.key);
+ if (taginfo.color) {
+ newMenuItem.setAttribute("style", `color: ${taginfo.color};`);
+ }
+ menuPopup.appendChild(newMenuItem);
+ }
+
+ this.appendChild(menulist);
+
+ updateParentNode(this.closest(".ruleaction"));
+ }
+ }
+
+ class MozRuleactiontargetPriority extends MozXULElement {
+ connectedCallback() {
+ this.appendChild(
+ MozXULElement.parseXULToFragment(
+ `
+ <menulist class="ruleactionitem" flex="1">
+ <menupopup>
+ <menuitem value="6" label="&highestPriorityCmd.label;"></menuitem>
+ <menuitem value="5" label="&highPriorityCmd.label;"></menuitem>
+ <menuitem value="4" label="&normalPriorityCmd.label;"></menuitem>
+ <menuitem value="3" label="&lowPriorityCmd.label;"></menuitem>
+ <menuitem value="2" label="&lowestPriorityCmd.label;"></menuitem>
+ </menupopup>
+ </menulist>
+ `,
+ ["chrome://messenger/locale/FilterEditor.dtd"]
+ )
+ );
+
+ updateParentNode(this.closest(".ruleaction"));
+ }
+ }
+
+ class MozRuleactiontargetJunkscore extends MozXULElement {
+ connectedCallback() {
+ this.appendChild(
+ MozXULElement.parseXULToFragment(
+ `
+ <menulist class="ruleactionitem" flex="1">
+ <menupopup>
+ <menuitem value="100" label="&junk.label;"/>
+ <menuitem value="0" label="&notJunk.label;"/>
+ </menupopup>
+ </menulist>
+ `,
+ ["chrome://messenger/locale/FilterEditor.dtd"]
+ )
+ );
+
+ updateParentNode(this.closest(".ruleaction"));
+ }
+ }
+
+ class MozRuleactiontargetReplyto extends MozXULElement {
+ connectedCallback() {
+ const menulist = document.createXULElement("menulist");
+ const menuPopup = document.createXULElement("menupopup");
+
+ menulist.classList.add("ruleactionitem");
+ menulist.setAttribute("flex", "1");
+ menulist.appendChild(menuPopup);
+
+ this.appendChild(menulist);
+
+ let ruleaction = this.closest(".ruleaction");
+ let raMenulist = ruleaction.querySelector(
+ '[is="ruleactiontype-menulist"]'
+ );
+ for (let { label, value } of raMenulist.findTemplates()) {
+ menulist.appendItem(label, value);
+ }
+ updateParentNode(ruleaction);
+ }
+ }
+
+ class MozRuleactiontargetForwardto extends MozXULElement {
+ connectedCallback() {
+ const input = document.createElementNS(
+ "http://www.w3.org/1999/xhtml",
+ "input"
+ );
+ input.classList.add("ruleactionitem", "input-inline");
+
+ this.classList.add("input-container");
+ this.appendChild(input);
+
+ updateParentNode(this.closest(".ruleaction"));
+ }
+ }
+
+ class MozRuleactiontargetFolder extends MozXULElement {
+ connectedCallback() {
+ this.appendChild(
+ MozXULElement.parseXULToFragment(
+ `
+ <menulist class="ruleactionitem
+ folderMenuItem"
+ flex="1"
+ displayformat="verbose">
+ <menupopup is="folder-menupopup"
+ mode="filing"
+ class="menulist-menupopup"
+ showRecent="true"
+ recentLabel="&recentFolders.label;"
+ showFileHereLabel="true">
+ </menupopup>
+ </menulist>
+ `,
+ ["chrome://messenger/locale/messenger.dtd"]
+ )
+ );
+
+ this.menulist = this.querySelector("menulist");
+
+ this.menulist.addEventListener("command", event => {
+ this.setPicker(event);
+ });
+
+ updateParentNode(this.closest(".ruleaction"));
+
+ let folder = this.menulist.value
+ ? MailUtils.getOrCreateFolder(this.menulist.value)
+ : gFilterList.folder;
+
+ // An account folder is not a move/copy target; show "Choose Folder".
+ folder = folder.isServer ? null : folder;
+
+ this.menulist.menupopup.selectFolder(folder);
+ }
+
+ setPicker(event) {
+ this.menulist.menupopup.selectFolder(event.target._folder);
+ }
+ }
+
+ class MozRuleactiontargetWrapper extends MozXULElement {
+ static get observedAttributes() {
+ return ["type"];
+ }
+
+ get ruleactiontargetElement() {
+ return this.node;
+ }
+
+ connectedCallback() {
+ this._updateAttributes();
+ }
+
+ attributeChangedCallback() {
+ this._updateAttributes();
+ }
+
+ _getChildNode(type) {
+ const elementMapping = {
+ movemessage: "ruleactiontarget-folder",
+ copymessage: "ruleactiontarget-folder",
+ setpriorityto: "ruleactiontarget-priority",
+ setjunkscore: "ruleactiontarget-junkscore",
+ forwardmessage: "ruleactiontarget-forwardto",
+ replytomessage: "ruleactiontarget-replyto",
+ addtagtomessage: "ruleactiontarget-tag",
+ };
+ const elementName = elementMapping[type];
+
+ return elementName ? document.createXULElement(elementName) : null;
+ }
+
+ _updateAttributes() {
+ if (!this.hasAttribute("type")) {
+ return;
+ }
+
+ const type = this.getAttribute("type");
+
+ while (this.lastChild) {
+ this.lastChild.remove();
+ }
+
+ if (type == null) {
+ return;
+ }
+
+ this.node = this._getChildNode(type);
+
+ if (this.node) {
+ this.node.setAttribute("flex", "1");
+ this.appendChild(this.node);
+ } else {
+ updateParentNode(this.closest(".ruleaction"));
+ }
+ }
+ }
+
+ customElements.define("ruleactiontarget-tag", MozRuleactiontargetTag);
+ customElements.define(
+ "ruleactiontarget-priority",
+ MozRuleactiontargetPriority
+ );
+ customElements.define(
+ "ruleactiontarget-junkscore",
+ MozRuleactiontargetJunkscore
+ );
+ customElements.define("ruleactiontarget-replyto", MozRuleactiontargetReplyto);
+ customElements.define(
+ "ruleactiontarget-forwardto",
+ MozRuleactiontargetForwardto
+ );
+ customElements.define("ruleactiontarget-folder", MozRuleactiontargetFolder);
+ customElements.define("ruleactiontarget-wrapper", MozRuleactiontargetWrapper);
+
+ /**
+ * This is an abstract class for search menulist general functionality.
+ *
+ * @abstract
+ * @augments MozXULElement
+ */
+ class MozSearchMenulistAbstract extends MozXULElement {
+ static get observedAttributes() {
+ return ["flex", "disabled"];
+ }
+
+ constructor() {
+ super();
+ this.internalScope = null;
+ this.internalValue = -1;
+ this.validityManager = Cc[
+ "@mozilla.org/mail/search/validityManager;1"
+ ].getService(Ci.nsIMsgSearchValidityManager);
+ }
+
+ connectedCallback() {
+ if (this.delayConnectedCallback() || this.hasChildNodes()) {
+ return;
+ }
+
+ this.menulist = document.createXULElement("menulist");
+ this.menulist.classList.add("search-menulist");
+ this.menulist.addEventListener("command", this.onSelect.bind(this));
+ this.menupopup = document.createXULElement("menupopup");
+ this.menupopup.classList.add("search-menulist-popup");
+ this.menulist.appendChild(this.menupopup);
+ this.appendChild(this.menulist);
+ this._updateAttributes();
+ }
+
+ attributeChangedCallback() {
+ this._updateAttributes();
+ }
+
+ _updateAttributes() {
+ if (!this.menulist) {
+ return;
+ }
+ if (this.hasAttribute("flex")) {
+ this.menulist.setAttribute("flex", this.getAttribute("flex"));
+ } else {
+ this.menulist.removeAttribute("flex");
+ }
+ if (this.hasAttribute("disabled")) {
+ this.menulist.setAttribute("disabled", this.getAttribute("disabled"));
+ } else {
+ this.menulist.removeAttribute("disabled");
+ }
+ }
+
+ set searchScope(val) {
+ // if scope isn't changing this is a noop
+ if (this.internalScope == val) {
+ return;
+ }
+ this.internalScope = val;
+ this.refreshList();
+ if (this.targets) {
+ this.targets.forEach(target => {
+ customElements.upgrade(target);
+ target.searchScope = val;
+ });
+ }
+ }
+
+ get searchScope() {
+ return this.internalScope;
+ }
+
+ get validityTable() {
+ return this.validityManager.getTable(this.searchScope);
+ }
+
+ get targets() {
+ const forAttrs = this.getAttribute("for");
+ if (!forAttrs) {
+ return null;
+ }
+ const targetIds = forAttrs.split(",");
+ if (targetIds.length == 0) {
+ return null;
+ }
+
+ return targetIds
+ .map(id => document.getElementById(id))
+ .filter(e => e != null);
+ }
+
+ get optargets() {
+ const forAttrs = this.getAttribute("opfor");
+ if (!forAttrs) {
+ return null;
+ }
+ const optargetIds = forAttrs.split(",");
+ if (optargetIds.length == 0) {
+ return null;
+ }
+
+ return optargetIds
+ .map(id => document.getElementById(id))
+ .filter(e => e != null);
+ }
+
+ set value(val) {
+ if (this.internalValue == val) {
+ return;
+ }
+ this.internalValue = val;
+ this.menulist.selectedItem = this.validMenuitem;
+ // now notify targets of new parent's value
+ if (this.targets) {
+ this.targets.forEach(target => {
+ customElements.upgrade(target);
+ target.parentValue = val;
+ });
+ }
+ // now notify optargets of new op parent's value
+ if (this.optargets) {
+ this.optargets.forEach(optarget => {
+ customElements.upgrade(optarget);
+ optarget.opParentValue = val;
+ });
+ }
+ }
+
+ get value() {
+ return this.internalValue;
+ }
+
+ /**
+ * Gets the label of the menulist's selected item.
+ */
+ get label() {
+ return this.menulist.selectedItem.getAttribute("label");
+ }
+
+ get validMenuitem() {
+ if (this.value == -1) {
+ // -1 means not initialized
+ return null;
+ }
+ let isCustom = isNaN(this.value);
+ let typedValue = isCustom ? this.value : parseInt(this.value);
+ // custom attribute to style the unavailable menulist item
+ this.menulist.setAttribute(
+ "unavailable",
+ !this.valueIds.includes(typedValue) ? "true" : null
+ );
+ // add a hidden menulist item if value is missing
+ let menuitem = this.menulist.querySelector(`[value="${this.value}"]`);
+ if (!menuitem) {
+ // need to add a hidden menuitem
+ menuitem = this.menulist.appendItem(this.valueLabel, this.value);
+ menuitem.hidden = true;
+ }
+ return menuitem;
+ }
+
+ refreshList(dontRestore) {
+ const menuItemIds = this.valueIds;
+ const menuItemStrings = this.valueStrings;
+ const popup = this.menupopup;
+ // save our old "value" so we can restore it later
+ let oldData;
+ if (!dontRestore) {
+ oldData = this.menulist.value;
+ }
+ // remove the old popup children
+ while (popup.hasChildNodes()) {
+ popup.lastChild.remove();
+ }
+ let newSelection;
+ let customizePos = -1;
+ for (let i = 0; i < menuItemIds.length; i++) {
+ // create the menuitem
+ if (Ci.nsMsgSearchAttrib.OtherHeader == menuItemIds[i].toString()) {
+ customizePos = i;
+ } else {
+ const menuitem = document.createXULElement("menuitem");
+ menuitem.setAttribute("label", menuItemStrings[i]);
+ menuitem.setAttribute("value", menuItemIds[i]);
+ popup.appendChild(menuitem);
+ // try to restore the selection
+ if (!newSelection || oldData == menuItemIds[i].toString()) {
+ newSelection = menuitem;
+ }
+ }
+ }
+ if (customizePos != -1) {
+ const separator = document.createXULElement("menuseparator");
+ popup.appendChild(separator);
+ const menuitem = document.createXULElement("menuitem");
+ menuitem.setAttribute("label", menuItemStrings[customizePos]);
+ menuitem.setAttribute("value", menuItemIds[customizePos]);
+ popup.appendChild(menuitem);
+ }
+
+ // If we are either uninitialized, or if we are called because
+ // of a change in our parent, update the value to the
+ // default stored in newSelection.
+ if ((this.value == -1 || dontRestore) && newSelection) {
+ this.value = newSelection.getAttribute("value");
+ }
+ this.menulist.selectedItem = this.validMenuitem;
+ }
+
+ onSelect(event) {
+ if (this.menulist.value == Ci.nsMsgSearchAttrib.OtherHeader) {
+ // Customize menuitem selected.
+ let args = {};
+ window.openDialog(
+ "chrome://messenger/content/CustomHeaders.xhtml",
+ "",
+ "modal,centerscreen,resizable,titlebar,chrome",
+ args
+ );
+ // User may have removed the custom header currently selected
+ // in the menulist so temporarily set the selection to a safe value.
+ this.value = Ci.nsMsgSearchAttrib.OtherHeader;
+ // rebuild the menulist
+ UpdateAfterCustomHeaderChange();
+ // Find the created or chosen custom header and select it.
+ let menuitem = null;
+ if (args.selectedVal) {
+ menuitem = this.menulist.querySelector(
+ `[label="${args.selectedVal}"]`
+ );
+ }
+ if (menuitem) {
+ this.value = menuitem.value;
+ } else {
+ // Nothing was picked in the custom headers editor so just pick something
+ // instead of the current "Customize" menuitem.
+ this.value = this.menulist.getItemAtIndex(0).value;
+ }
+ } else {
+ this.value = this.menulist.value;
+ }
+ }
+ }
+
+ /**
+ * The MozSearchAttribute widget is typically used in the search and filter dialogs to show a list
+ * of possible message headers.
+ *
+ * @augments MozSearchMenulistAbstract
+ */
+ class MozSearchAttribute extends MozSearchMenulistAbstract {
+ constructor() {
+ super();
+
+ this.stringBundle = Services.strings.createBundle(
+ "chrome://messenger/locale/search-attributes.properties"
+ );
+ }
+
+ connectedCallback() {
+ super.connectedCallback();
+
+ initializeTermFromId(this.id);
+ }
+
+ get valueLabel() {
+ if (isNaN(this.value)) {
+ // is this a custom term?
+ let customTerm = MailServices.filters.getCustomTerm(this.value);
+ if (customTerm) {
+ return customTerm.name;
+ }
+ // The custom term may be missing after the extension that added it
+ // was disabled or removed. We need to notify the user.
+ let scriptError = Cc["@mozilla.org/scripterror;1"].createInstance(
+ Ci.nsIScriptError
+ );
+ scriptError.init(
+ "Missing custom search term " + this.value,
+ null,
+ null,
+ 0,
+ 0,
+ Ci.nsIScriptError.errorFlag,
+ "component javascript"
+ );
+ Services.console.logMessage(scriptError);
+ return this.stringBundle.GetStringFromName("MissingCustomTerm");
+ }
+ return this.stringBundle.GetStringFromName(
+ this.validityManager.getAttributeProperty(parseInt(this.value))
+ );
+ }
+
+ get valueIds() {
+ let result = this.validityTable.getAvailableAttributes();
+ // add any available custom search terms
+ for (let customTerm of MailServices.filters.getCustomTerms()) {
+ // For custom terms, the array element is a string with the custom id
+ // instead of the integer attribute
+ if (customTerm.getAvailable(this.searchScope, null)) {
+ result.push(customTerm.id);
+ }
+ }
+ return result;
+ }
+
+ get valueStrings() {
+ let strings = [];
+ let ids = this.valueIds;
+ let hdrsArray = null;
+ try {
+ let hdrs = Services.prefs.getCharPref("mailnews.customHeaders");
+ hdrs = hdrs.replace(/\s+/g, ""); // remove white spaces before splitting
+ hdrsArray = hdrs.match(/[^:]+/g);
+ } catch (ex) {}
+ let j = 0;
+ for (let i = 0; i < ids.length; i++) {
+ if (isNaN(ids[i])) {
+ // Is this a custom search term?
+ let customTerm = MailServices.filters.getCustomTerm(ids[i]);
+ if (customTerm) {
+ strings[i] = customTerm.name;
+ } else {
+ strings[i] = "";
+ }
+ } else if (ids[i] > Ci.nsMsgSearchAttrib.OtherHeader && hdrsArray) {
+ strings[i] = hdrsArray[j++];
+ } else {
+ strings[i] = this.stringBundle.GetStringFromName(
+ this.validityManager.getAttributeProperty(ids[i])
+ );
+ }
+ }
+ return strings;
+ }
+ }
+ customElements.define("search-attribute", MozSearchAttribute);
+
+ /**
+ * MozSearchOperator contains a list of operators that can be applied on search-attribute and
+ * search-value value.
+ *
+ * @augments MozSearchMenulistAbstract
+ */
+ class MozSearchOperator extends MozSearchMenulistAbstract {
+ constructor() {
+ super();
+
+ this.stringBundle = Services.strings.createBundle(
+ "chrome://messenger/locale/search-operators.properties"
+ );
+ }
+
+ connectedCallback() {
+ super.connectedCallback();
+
+ this.searchAttribute = Ci.nsMsgSearchAttrib.Default;
+ }
+
+ get valueLabel() {
+ return this.stringBundle.GetStringFromName(this.value);
+ }
+
+ get valueIds() {
+ let isCustom = isNaN(this.searchAttribute);
+ if (isCustom) {
+ let customTerm = MailServices.filters.getCustomTerm(
+ this.searchAttribute
+ );
+ if (customTerm) {
+ return customTerm.getAvailableOperators(this.searchScope);
+ }
+ return [Ci.nsMsgSearchOp.Contains];
+ }
+ return this.validityTable.getAvailableOperators(this.searchAttribute);
+ }
+
+ get valueStrings() {
+ let strings = [];
+ let ids = this.valueIds;
+ for (let i = 0; i < ids.length; i++) {
+ strings[i] = this.stringBundle.GetStringFromID(ids[i]);
+ }
+ return strings;
+ }
+
+ set parentValue(val) {
+ if (
+ this.searchAttribute == val &&
+ val != Ci.nsMsgSearchAttrib.OtherHeader
+ ) {
+ return;
+ }
+ this.searchAttribute = val;
+ this.refreshList(true); // don't restore the selection, since searchvalue nulls it
+ if (val == Ci.nsMsgSearchAttrib.AgeInDays) {
+ // We want "Age in Days" to default to "is less than".
+ this.value = Ci.nsMsgSearchOp.IsLessThan;
+ }
+ }
+
+ get parentValue() {
+ return this.searchAttribute;
+ }
+ }
+ customElements.define("search-operator", MozSearchOperator);
+
+ /**
+ * MozSearchValue is a widget that allows selecting the value to search or filter on. It can be a
+ * text entry, priority, status, junk status, tags, hasAttachment status, and addressbook etc.
+ *
+ * @augments MozXULElement
+ */
+ class MozSearchValue extends MozXULElement {
+ static get observedAttributes() {
+ return ["disabled"];
+ }
+
+ constructor() {
+ super();
+
+ this.addEventListener("keypress", event => {
+ if (event.keyCode != KeyEvent.DOM_VK_RETURN) {
+ return;
+ }
+ onEnterInSearchTerm(event);
+ });
+
+ this.internalOperator = null;
+ this.internalAttribute = null;
+ this.internalValue = null;
+
+ this.inputType = "none";
+ }
+
+ connectedCallback() {
+ this.classList.add("input-container");
+ }
+
+ static get stringBundle() {
+ if (!this._stringBundle) {
+ this._stringBundle = Services.strings.createBundle(
+ "chrome://messenger/locale/messenger.properties"
+ );
+ }
+ return this._stringBundle;
+ }
+
+ /**
+ * Create a menulist to be used as the input.
+ *
+ * @param {object[]} itemDataList - An ordered list of items to add to the
+ * menulist. Each entry must have a 'value' property to be used as the
+ * item value. If the entry has a 'label' property, it will be used
+ * directly as the item label, otherwise it must identify a bundle string
+ * using the 'stringId' property.
+ *
+ * @returns {MozMenuList} - The newly created menulist.
+ */
+ static _createMenulist(itemDataList) {
+ let menulist = document.createXULElement("menulist");
+ menulist.classList.add("search-value-menulist");
+ let menupopup = document.createXULElement("menupopup");
+ menupopup.classList.add("search-value-popup");
+
+ let bundle = this.stringBundle;
+
+ for (let itemData of itemDataList) {
+ let item = document.createXULElement("menuitem");
+ item.classList.add("search-value-menuitem");
+ item.label =
+ itemData.label || bundle.GetStringFromName(itemData.stringId);
+ item.value = itemData.value;
+ menupopup.appendChild(item);
+ }
+ menulist.appendChild(menupopup);
+ return menulist;
+ }
+
+ /**
+ * Set the child input. The input will only be changed if the type changes.
+ *
+ * @param {string} type - The type of input to use.
+ * @param {string|number|undefined} value - A value to set on the input, or
+ * leave undefined to not change the value. See setInputValue.
+ */
+ setInput(type, value) {
+ if (type != this.inputType) {
+ this.inputType = type;
+ this.input?.remove();
+ let input;
+ switch (type) {
+ case "text":
+ input = document.createElement("input");
+ input.classList.add("input-inline", "search-value-input");
+ break;
+ case "date":
+ input = document.createElement("input");
+ input.classList.add("input-inline", "search-value-input");
+ if (!value) {
+ // Newly created date input shows today's date.
+ // value is expected in microseconds since epoch.
+ value = Date.now() * 1000;
+ }
+ break;
+ case "size":
+ input = document.createElement("input");
+ input.type = "number";
+ input.min = 0;
+ input.max = 1000000000;
+ input.classList.add("input-inline", "search-value-input");
+ break;
+ case "age":
+ input = document.createElement("input");
+ input.type = "number";
+ input.min = -40000; // ~100 years.
+ input.max = 40000;
+ input.classList.add("input-inline", "search-value-input");
+ break;
+ case "percent":
+ input = document.createElement("input");
+ input.type = "number";
+ input.min = 0;
+ input.max = 100;
+ input.classList.add("input-inline", "search-value-input");
+ break;
+ case "priority":
+ input = this.constructor._createMenulist([
+ { stringId: "priorityHighest", value: Ci.nsMsgPriority.highest },
+ { stringId: "priorityHigh", value: Ci.nsMsgPriority.high },
+ { stringId: "priorityNormal", value: Ci.nsMsgPriority.normal },
+ { stringId: "priorityLow", value: Ci.nsMsgPriority.low },
+ { stringId: "priorityLowest", value: Ci.nsMsgPriority.lowest },
+ ]);
+ break;
+ case "status":
+ input = this.constructor._createMenulist([
+ { stringId: "replied", value: Ci.nsMsgMessageFlags.Replied },
+ { stringId: "read", value: Ci.nsMsgMessageFlags.Read },
+ { stringId: "new", value: Ci.nsMsgMessageFlags.New },
+ { stringId: "forwarded", value: Ci.nsMsgMessageFlags.Forwarded },
+ { stringId: "flagged", value: Ci.nsMsgMessageFlags.Marked },
+ ]);
+ break;
+ case "addressbook":
+ input = document.createXULElement("menulist", {
+ is: "menulist-addrbooks",
+ });
+ input.setAttribute("localonly", "true");
+ input.classList.add("search-value-menulist");
+ if (!value) {
+ // Select the personal addressbook by default.
+ value = "jsaddrbook://abook.sqlite";
+ }
+ break;
+ case "tags":
+ input = this.constructor._createMenulist(
+ MailServices.tags.getAllTags().map(taginfo => {
+ return { label: taginfo.tag, value: taginfo.key };
+ })
+ );
+ break;
+ case "junk-status":
+ // "Junk Status is/isn't/is empty/isn't empty 'Junk'".
+ input = this.constructor._createMenulist([
+ { stringId: "junk", value: Ci.nsIJunkMailPlugin.JUNK },
+ ]);
+ break;
+ case "attachment-status":
+ // "Attachment Status is/isn't 'Has Attachments'".
+ input = this.constructor._createMenulist([
+ { stringId: "hasAttachments", value: "0" },
+ ]);
+ break;
+ case "junk-origin":
+ input = this.constructor._createMenulist([
+ { stringId: "junkScoreOriginPlugin", value: "plugin" },
+ { stringId: "junkScoreOriginUser", value: "user" },
+ { stringId: "junkScoreOriginFilter", value: "filter" },
+ { stringId: "junkScoreOriginWhitelist", value: "whitelist" },
+ { stringId: "junkScoreOriginImapFlag", value: "imapflag" },
+ ]);
+ break;
+ case "none":
+ input = null;
+ break;
+ case "custom":
+ // Used by extensions.
+ // FIXME: We need a better way for extensions to set a custom input.
+ input = document.createXULElement("hbox");
+ input.setAttribute("flex", "1");
+ input.classList.add("search-value-custom");
+ break;
+ default:
+ throw new Error(`Unrecognised input type "${type}"`);
+ }
+
+ this.input = input;
+ if (input) {
+ this.appendChild(input);
+ }
+
+ this._updateAttributes();
+ }
+
+ this.setInputValue(value);
+ }
+
+ /**
+ * Set the child input to the given value.
+ *
+ * @param {string|number} value - The value to set on the input. For "date"
+ * inputs, this should be a number of microseconds since the epoch.
+ */
+ setInputValue(value) {
+ if (value === undefined) {
+ return;
+ }
+ switch (this.inputType) {
+ case "text":
+ case "size":
+ case "age":
+ case "percent":
+ this.input.value = value;
+ break;
+ case "date":
+ this.input.value = convertPRTimeToString(value);
+ break;
+ case "priority":
+ case "status":
+ case "addressbook":
+ case "tags":
+ case "junk-status":
+ case "attachment-status":
+ case "junk-origin":
+ let item = this.input.querySelector(`menuitem[value="${value}"]`);
+ if (item) {
+ this.input.selectedItem = item;
+ }
+ break;
+ case "none":
+ // Silently ignore the value.
+ break;
+ case "custom":
+ this.input.setAttribute("value", value);
+ break;
+ default:
+ throw new Error(`Unhandled input type "${this.inputType}"`);
+ }
+ }
+
+ /**
+ * Get the child input's value.
+ *
+ * @returns {string|number} - The value set in the input. For "date"
+ * inputs, this is the number of microseconds since the epoch.
+ */
+ getInputValue() {
+ switch (this.inputType) {
+ case "text":
+ case "size":
+ case "age":
+ case "percent":
+ return this.input.value;
+ case "date":
+ return convertStringToPRTime(this.input.value);
+ case "priority":
+ case "status":
+ case "addressbook":
+ case "tags":
+ case "junk-status":
+ case "attachment-status":
+ case "junk-origin":
+ return this.input.selectedItem.value;
+ case "none":
+ return "";
+ case "custom":
+ return this.input.getAttribute("value");
+ default:
+ throw new Error(`Unhandled input type "${this.inputType}"`);
+ }
+ }
+
+ /**
+ * Get the element's displayed value.
+ *
+ * @returns {string} - The value seen by the user.
+ */
+ getReadableValue() {
+ switch (this.inputType) {
+ case "text":
+ case "size":
+ case "age":
+ case "percent":
+ case "date":
+ return this.input.value;
+ case "priority":
+ case "status":
+ case "addressbook":
+ case "tags":
+ case "junk-status":
+ case "attachment-status":
+ case "junk-origin":
+ return this.input.selectedItem.label;
+ case "none":
+ return "";
+ case "custom":
+ return this.input.getAttribute("value");
+ default:
+ throw new Error(`Unhandled input type "${this.inputType}"`);
+ }
+ }
+
+ attributeChangedCallback() {
+ this._updateAttributes();
+ }
+
+ _updateAttributes() {
+ if (!this.input) {
+ return;
+ }
+ if (this.hasAttribute("disabled")) {
+ this.input.setAttribute("disabled", this.getAttribute("disabled"));
+ } else {
+ this.input.removeAttribute("disabled");
+ }
+ }
+
+ /**
+ * Update the displayed input according to the selected sibling attributes
+ * and operators.
+ *
+ * @param {nsIMsgSearchValue} [value] - A value to display in the input. Or
+ * leave unset to not change the value.
+ */
+ updateDisplay(value) {
+ let operator = Number(this.internalOperator);
+ switch (Number(this.internalAttribute)) {
+ // Use the index to hide/show the appropriate child.
+ case Ci.nsMsgSearchAttrib.Priority:
+ this.setInput("priority", value?.priority);
+ break;
+ case Ci.nsMsgSearchAttrib.MsgStatus:
+ this.setInput("status", value?.status);
+ break;
+ case Ci.nsMsgSearchAttrib.Date:
+ this.setInput("date", value?.date);
+ break;
+ case Ci.nsMsgSearchAttrib.Sender:
+ case Ci.nsMsgSearchAttrib.To:
+ case Ci.nsMsgSearchAttrib.ToOrCC:
+ case Ci.nsMsgSearchAttrib.AllAddresses:
+ case Ci.nsMsgSearchAttrib.CC:
+ if (
+ operator == Ci.nsMsgSearchOp.IsntInAB ||
+ operator == Ci.nsMsgSearchOp.IsInAB
+ ) {
+ this.setInput("addressbook", value?.str);
+ } else {
+ this.setInput("text", value?.str);
+ }
+ break;
+ case Ci.nsMsgSearchAttrib.Keywords:
+ this.setInput(
+ operator == Ci.nsMsgSearchOp.IsEmpty ||
+ operator == Ci.nsMsgSearchOp.IsntEmpty
+ ? "none"
+ : "tags",
+ value?.str
+ );
+ break;
+ case Ci.nsMsgSearchAttrib.JunkStatus:
+ this.setInput(
+ operator == Ci.nsMsgSearchOp.IsEmpty ||
+ operator == Ci.nsMsgSearchOp.IsntEmpty
+ ? "none"
+ : "junk-status",
+ value?.junkStatus
+ );
+ break;
+ case Ci.nsMsgSearchAttrib.HasAttachmentStatus:
+ this.setInput("attachment-status", value?.hasAttachmentStatus);
+ break;
+ case Ci.nsMsgSearchAttrib.JunkScoreOrigin:
+ this.setInput("junk-origin", value?.str);
+ break;
+ case Ci.nsMsgSearchAttrib.AgeInDays:
+ this.setInput("age", value?.age);
+ break;
+ case Ci.nsMsgSearchAttrib.Size:
+ this.setInput("size", value?.size);
+ break;
+ case Ci.nsMsgSearchAttrib.JunkPercent:
+ this.setInput("percent", value?.junkPercent);
+ break;
+ default:
+ if (isNaN(this.internalAttribute)) {
+ // Custom attribute, the internalAttribute is a string.
+ // FIXME: We need a better way for extensions to set a custom input.
+ this.setInput("custom", value?.str);
+ this.input.setAttribute("searchAttribute", this.internalAttribute);
+ } else {
+ this.setInput("text", value?.str);
+ }
+ break;
+ }
+ }
+
+ /**
+ * The sibling operator type.
+ *
+ * @type {nsMsgSearchOpValue}
+ */
+ set opParentValue(val) {
+ if (this.internalOperator == val) {
+ return;
+ }
+ this.internalOperator = val;
+ this.updateDisplay();
+ }
+
+ get opParentValue() {
+ return this.internalOperator;
+ }
+
+ /**
+ * A duplicate of the searchAttribute property.
+ *
+ * @type {nsMsgSearchAttribValue}
+ */
+ set parentValue(val) {
+ this.searchAttribute = val;
+ }
+
+ get parentValue() {
+ return this.searchAttribute;
+ }
+
+ /**
+ * The sibling attribute type.
+ *
+ * @type {nsMsgSearchAttribValue}
+ */
+ set searchAttribute(val) {
+ if (this.internalAttribute == val) {
+ return;
+ }
+ this.internalAttribute = val;
+ this.updateDisplay();
+ }
+
+ get searchAttribute() {
+ return this.internalAttribute;
+ }
+
+ /**
+ * The stored value for this element.
+ *
+ * Note that the input value is *derived* from this object when it is set.
+ * But changes to the input value using the UI will not change the stored
+ * value until the save method is called.
+ *
+ * @type {nsIMsgSearchValue}
+ */
+ set value(val) {
+ // val is a nsIMsgSearchValue object
+ this.internalValue = val;
+ this.updateDisplay(val);
+ }
+
+ get value() {
+ return this.internalValue;
+ }
+
+ /**
+ * Updates the stored value for this element to reflect its current input
+ * value.
+ */
+ save() {
+ let searchValue = this.value;
+ let searchAttribute = this.searchAttribute;
+
+ searchValue.attrib = isNaN(searchAttribute)
+ ? Ci.nsMsgSearchAttrib.Custom
+ : searchAttribute;
+ switch (Number(searchAttribute)) {
+ case Ci.nsMsgSearchAttrib.Priority:
+ searchValue.priority = this.getInputValue();
+ break;
+ case Ci.nsMsgSearchAttrib.MsgStatus:
+ searchValue.status = this.getInputValue();
+ break;
+ case Ci.nsMsgSearchAttrib.AgeInDays:
+ searchValue.age = this.getInputValue();
+ break;
+ case Ci.nsMsgSearchAttrib.Date:
+ searchValue.date = this.getInputValue();
+ break;
+ case Ci.nsMsgSearchAttrib.JunkStatus:
+ searchValue.junkStatus = this.getInputValue();
+ break;
+ case Ci.nsMsgSearchAttrib.HasAttachmentStatus:
+ searchValue.status = Ci.nsMsgMessageFlags.Attachment;
+ break;
+ case Ci.nsMsgSearchAttrib.JunkPercent:
+ searchValue.junkPercent = this.getInputValue();
+ break;
+ case Ci.nsMsgSearchAttrib.Size:
+ searchValue.size = this.getInputValue();
+ break;
+ default:
+ searchValue.str = this.getInputValue();
+ break;
+ }
+ }
+
+ /**
+ * Stores the displayed value for this element in the given object.
+ *
+ * Note that after this call, the stored value will remain pointing to the
+ * given searchValue object.
+ *
+ * @param {nsIMsgSearchValue} searchValue - The object to store the
+ * displayed value in.
+ */
+ saveTo(searchValue) {
+ this.internalValue = searchValue;
+ this.save();
+ }
+ }
+ customElements.define("search-value", MozSearchValue);
+
+ // The menulist CE is defined lazily. Create one now to get menulist defined,
+ // allowing us to inherit from it.
+ if (!customElements.get("menulist")) {
+ delete document.createXULElement("menulist");
+ }
+ {
+ /**
+ * The MozRuleactiontypeMenulist is a widget that allows selecting the actions from the given menulist for
+ * the selected folder. It gets displayed in the message filter dialog box.
+ *
+ * @augments {MozMenuList}
+ */
+ class MozRuleactiontypeMenulist extends customElements.get("menulist") {
+ connectedCallback() {
+ super.connectedCallback();
+ if (this.delayConnectedCallback() || this.hasConnected) {
+ return;
+ }
+ this.hasConnected = true;
+
+ this.setAttribute("is", "ruleactiontype-menulist");
+ this.addEventListener("command", event => {
+ this.parentNode.setAttribute("value", this.value);
+ checkActionsReorder();
+ });
+
+ this.addEventListener("popupshowing", event => {
+ let unavailableActions = this.usedActionsList();
+ for (let index = 0; index < this.menuitems.length; index++) {
+ let menu = this.menuitems[index];
+ menu.setAttribute("disabled", menu.value in unavailableActions);
+ }
+ });
+
+ this.menuitems = this.getElementsByTagNameNS(
+ this.namespaceURI,
+ "menuitem"
+ );
+
+ // Force initialization of the menulist custom element first.
+ customElements.upgrade(this);
+ this.addCustomActions();
+ this.hideInvalidActions();
+ // Differentiate between creating a new, next available action,
+ // and creating a row which will be initialized with an action.
+ if (!this.parentNode.hasAttribute("initialActionIndex")) {
+ let unavailableActions = this.usedActionsList();
+ // Select the first one that's not in the list.
+ for (let index = 0; index < this.menuitems.length; index++) {
+ let menu = this.menuitems[index];
+ if (!(menu.value in unavailableActions) && !menu.hidden) {
+ this.value = menu.value;
+ this.parentNode.setAttribute("value", menu.value);
+ break;
+ }
+ }
+ } else {
+ this.parentNode.mActionTypeInitialized = true;
+ this.parentNode.clearInitialActionIndex();
+ }
+ }
+
+ hideInvalidActions() {
+ let menupopup = this.menupopup;
+ let scope = getScopeFromFilterList(gFilterList);
+
+ // Walk through the list of filter actions and hide any actions which aren't valid
+ // for our given scope (news, imap, pop, etc) and context.
+ let elements;
+
+ // Disable / enable all elements in the "filteractionlist"
+ // based on the scope and the "enablefornews" attribute.
+ elements = menupopup.getElementsByAttribute("enablefornews", "true");
+ for (let i = 0; i < elements.length; i++) {
+ elements[i].hidden = scope != Ci.nsMsgSearchScope.newsFilter;
+ }
+
+ elements = menupopup.getElementsByAttribute("enablefornews", "false");
+ for (let i = 0; i < elements.length; i++) {
+ elements[i].hidden = scope == Ci.nsMsgSearchScope.newsFilter;
+ }
+
+ elements = menupopup.getElementsByAttribute("enableforpop3", "true");
+ for (let i = 0; i < elements.length; i++) {
+ elements[i].hidden = !(
+ gFilterList.folder.server.type == "pop3" ||
+ gFilterList.folder.server.type == "none"
+ );
+ }
+
+ elements = menupopup.getElementsByAttribute("isCustom", "true");
+ // Note there might be an additional element here as a placeholder
+ // for a missing action, so we iterate over the known actions
+ // instead of the elements.
+ for (let i = 0; i < gCustomActions.length; i++) {
+ elements[i].hidden = !gCustomActions[i].isValidForType(
+ gFilterType,
+ scope
+ );
+ }
+
+ // Disable "Reply with Template" if there are no templates.
+ if (this.findTemplates().length == 0) {
+ elements = menupopup.getElementsByAttribute(
+ "value",
+ "replytomessage"
+ );
+ if (elements.length == 1) {
+ elements[0].hidden = true;
+ }
+ }
+ }
+
+ addCustomActions() {
+ let menupopup = this.menupopup;
+ for (let i = 0; i < gCustomActions.length; i++) {
+ let customAction = gCustomActions[i];
+ let menuitem = document.createXULElement("menuitem");
+ menuitem.setAttribute("label", customAction.name);
+ menuitem.setAttribute("value", customAction.id);
+ menuitem.setAttribute("isCustom", "true");
+ menupopup.appendChild(menuitem);
+ }
+ }
+
+ /**
+ * Returns a hash containing all of the filter actions which are currently
+ * being used by other filteractionrows.
+ *
+ * @returns {object} - a hash containing all of the filter actions which are
+ * currently being used by other filteractionrows.
+ */
+ usedActionsList() {
+ let usedActions = {};
+ let currentFilterActionRow = this.parentNode;
+ let listBox = currentFilterActionRow.parentNode; // need to account for the list item.
+ // Now iterate over each list item in the list box.
+ for (let index = 0; index < listBox.getRowCount(); index++) {
+ let filterActionRow = listBox.getItemAtIndex(index);
+ if (filterActionRow != currentFilterActionRow) {
+ let actionValue = filterActionRow.getAttribute("value");
+
+ // Let custom actions decide if dups are allowed.
+ let isCustom = false;
+ for (let i = 0; i < gCustomActions.length; i++) {
+ if (gCustomActions[i].id == actionValue) {
+ isCustom = true;
+ if (!gCustomActions[i].allowDuplicates) {
+ usedActions[actionValue] = true;
+ }
+ break;
+ }
+ }
+
+ if (!isCustom) {
+ // The following actions can appear more than once in a single filter
+ // so do not set them as already used.
+ if (
+ actionValue != "addtagtomessage" &&
+ actionValue != "forwardmessage" &&
+ actionValue != "copymessage"
+ ) {
+ usedActions[actionValue] = true;
+ }
+ // If either Delete message or Move message exists, disable the other one.
+ // It does not make sense to apply both to the same message.
+ if (actionValue == "deletemessage") {
+ usedActions.movemessage = true;
+ } else if (actionValue == "movemessage") {
+ usedActions.deletemessage = true;
+ } else if (actionValue == "markasread") {
+ // The same with Mark as read/Mark as Unread.
+ usedActions.markasunread = true;
+ } else if (actionValue == "markasunread") {
+ usedActions.markasread = true;
+ }
+ }
+ }
+ }
+ return usedActions;
+ }
+
+ /**
+ * Check if there exist any templates in this account.
+ *
+ * @returns {object[]} - An array of template headers: each has a label and
+ * a value.
+ */
+ findTemplates() {
+ let identities = MailServices.accounts.getIdentitiesForServer(
+ gFilterList.folder.server
+ );
+ // Typically if this is Local Folders.
+ if (identities.length == 0) {
+ if (MailServices.accounts.defaultAccount) {
+ identities.push(
+ MailServices.accounts.defaultAccount.defaultIdentity
+ );
+ }
+ }
+
+ let templates = [];
+ let foldersScanned = [];
+
+ for (let identity of identities) {
+ let enumerator = null;
+ let msgFolder = MailUtils.getExistingFolder(
+ identity.stationeryFolder
+ );
+ // If we already processed this folder, do not set enumerator
+ // so that we skip this identity.
+ if (msgFolder && !foldersScanned.includes(msgFolder)) {
+ foldersScanned.push(msgFolder);
+ enumerator = msgFolder.msgDatabase.enumerateMessages();
+ }
+
+ if (!enumerator) {
+ continue;
+ }
+
+ for (let header of enumerator) {
+ let uri =
+ msgFolder.URI +
+ "?messageId=" +
+ header.messageId +
+ "&subject=" +
+ header.mime2DecodedSubject;
+ templates.push({ label: header.mime2DecodedSubject, value: uri });
+ }
+ }
+ return templates;
+ }
+ }
+
+ customElements.define(
+ "ruleactiontype-menulist",
+ MozRuleactiontypeMenulist,
+ { extends: "menulist" }
+ );
+ }
+
+ /**
+ * The MozRuleactionRichlistitem is a widget which gives the options to filter
+ * the messages with following elements: ruleactiontype-menulist, ruleactiontarget-wrapper
+ * and button to add or remove the MozRuleactionRichlistitem. It gets added in the
+ * filterActionList richlistbox in the Filter Editor dialog.
+ *
+ * @augments {MozElements.MozRichlistitem}
+ */
+ class MozRuleactionRichlistitem extends MozElements.MozRichlistitem {
+ static get inheritedAttributes() {
+ return { ".ruleactiontarget": "type=value" };
+ }
+
+ constructor() {
+ super();
+
+ this.mActionTypeInitialized = false;
+ this.mRuleActionTargetInitialized = false;
+ }
+
+ connectedCallback() {
+ if (this.delayConnectedCallback() || this.hasChildNodes()) {
+ return;
+ }
+ this.setAttribute("is", "ruleaction-richlistitem");
+ this.appendChild(
+ MozXULElement.parseXULToFragment(
+ `
+ <menulist is="ruleactiontype-menulist" style="flex: &filterActionTypeFlexValue;">
+ <menupopup>
+ <menuitem label="&moveMessage.label;"
+ value="movemessage"
+ enablefornews="false"></menuitem>
+ <menuitem label="&copyMessage.label;"
+ value="copymessage"></menuitem>
+ <menuseparator enablefornews="false"></menuseparator>
+ <menuitem label="&forwardTo.label;"
+ value="forwardmessage"
+ enablefornews="false"></menuitem>
+ <menuitem label="&replyWithTemplate.label;"
+ value="replytomessage"
+ enablefornews="false"></menuitem>
+ <menuseparator></menuseparator>
+ <menuitem label="&markMessageRead.label;"
+ value="markasread"></menuitem>
+ <menuitem label="&markMessageUnread.label;"
+ value="markasunread"></menuitem>
+ <menuitem label="&markMessageStarred.label;"
+ value="markasflagged"></menuitem>
+ <menuitem label="&setPriority.label;"
+ value="setpriorityto"></menuitem>
+ <menuitem label="&addTag.label;"
+ value="addtagtomessage"></menuitem>
+ <menuitem label="&setJunkScore.label;"
+ value="setjunkscore"
+ enablefornews="false"></menuitem>
+ <menuseparator enableforpop3="true"></menuseparator>
+ <menuitem label="&deleteMessage.label;"
+ value="deletemessage"></menuitem>
+ <menuitem label="&deleteFromPOP.label;"
+ value="deletefrompopserver"
+ enableforpop3="true"></menuitem>
+ <menuitem label="&fetchFromPOP.label;"
+ value="fetchfrompopserver"
+ enableforpop3="true"></menuitem>
+ <menuseparator></menuseparator>
+ <menuitem label="&ignoreThread.label;"
+ value="ignorethread"></menuitem>
+ <menuitem label="&ignoreSubthread.label;"
+ value="ignoresubthread"></menuitem>
+ <menuitem label="&watchThread.label;"
+ value="watchthread"></menuitem>
+ <menuseparator></menuseparator>
+ <menuitem label="&stopExecution.label;"
+ value="stopexecution"></menuitem>
+ </menupopup>
+ </menulist>
+ <ruleactiontarget-wrapper class="ruleactiontarget"
+ style="flex: &filterActionTargetFlexValue;">
+ </ruleactiontarget-wrapper>
+ <hbox>
+ <button class="small-button"
+ label="+"
+ tooltiptext="&addAction.tooltip;"
+ oncommand="this.parentNode.parentNode.addRow();"></button>
+ <button class="small-button remove-small-button"
+ label="−"
+ tooltiptext="&removeAction.tooltip;"
+ oncommand="this.parentNode.parentNode.removeRow();"></button>
+ </hbox>
+ `,
+ [
+ "chrome://messenger/locale/messenger.dtd",
+ "chrome://messenger/locale/FilterEditor.dtd",
+ ]
+ )
+ );
+
+ this.mRuleActionType = this.querySelector("menulist");
+ this.mRemoveButton = this.querySelector(".remove-small-button");
+ this.mListBox = this.parentNode;
+ this.initializeAttributeInheritance();
+ }
+
+ set selected(val) {
+ // This provides a dummy selected property that the richlistbox expects to
+ // be able to call. See bug 202036.
+ }
+
+ get selected() {
+ return false;
+ }
+
+ _fireEvent(aName) {
+ // This provides a dummy _fireEvent function that the richlistbox expects to
+ // be able to call. See bug 202036.
+ }
+
+ /**
+ * We should only remove the initialActionIndex after we have been told that
+ * both the rule action type and the rule action target have both been built
+ * since they both need this piece of information. This complication arises
+ * because both of these child elements are getting bound asynchronously
+ * after the search row has been constructed.
+ */
+ clearInitialActionIndex() {
+ if (this.mActionTypeInitialized && this.mRuleActionTargetInitialized) {
+ this.removeAttribute("initialActionIndex");
+ }
+ }
+
+ initWithAction(aFilterAction) {
+ let filterActionStr;
+ let actionTarget = this.children[1];
+ let actionItem = actionTarget.ruleactiontargetElement;
+ let nsMsgFilterAction = Ci.nsMsgFilterAction;
+ switch (aFilterAction.type) {
+ case nsMsgFilterAction.Custom:
+ filterActionStr = aFilterAction.customId;
+ if (actionItem) {
+ actionItem.children[0].value = aFilterAction.strValue;
+ }
+
+ // Make sure the custom action has been added. If not, it
+ // probably was from an extension that has been removed. We'll
+ // show a dummy menuitem to warn the user.
+ let needCustomLabel = true;
+ for (let i = 0; i < gCustomActions.length; i++) {
+ if (gCustomActions[i].id == filterActionStr) {
+ needCustomLabel = false;
+ break;
+ }
+ }
+ if (needCustomLabel) {
+ let menuitem = document.createXULElement("menuitem");
+ menuitem.setAttribute(
+ "label",
+ gFilterBundle.getString("filterMissingCustomAction")
+ );
+ menuitem.setAttribute("value", filterActionStr);
+ menuitem.disabled = true;
+ this.mRuleActionType.menupopup.appendChild(menuitem);
+ let scriptError = Cc["@mozilla.org/scripterror;1"].createInstance(
+ Ci.nsIScriptError
+ );
+ scriptError.init(
+ "Missing custom action " + filterActionStr,
+ null,
+ null,
+ 0,
+ 0,
+ Ci.nsIScriptError.errorFlag,
+ "component javascript"
+ );
+ Services.console.logMessage(scriptError);
+ }
+ break;
+ case nsMsgFilterAction.MoveToFolder:
+ case nsMsgFilterAction.CopyToFolder:
+ actionItem.children[0].value = aFilterAction.targetFolderUri;
+ break;
+ case nsMsgFilterAction.Reply:
+ case nsMsgFilterAction.Forward:
+ actionItem.children[0].value = aFilterAction.strValue;
+ break;
+ case nsMsgFilterAction.ChangePriority:
+ actionItem.children[0].value = aFilterAction.priority;
+ break;
+ case nsMsgFilterAction.JunkScore:
+ actionItem.children[0].value = aFilterAction.junkScore;
+ break;
+ case nsMsgFilterAction.AddTag:
+ actionItem.children[0].value = aFilterAction.strValue;
+ break;
+ default:
+ break;
+ }
+ if (aFilterAction.type != nsMsgFilterAction.Custom) {
+ filterActionStr = gFilterActionStrings[aFilterAction.type];
+ }
+ this.mRuleActionType.value = filterActionStr;
+ this.mRuleActionTargetInitialized = true;
+ this.clearInitialActionIndex();
+ checkActionsReorder();
+ }
+
+ /**
+ * Function is used to check if the filter is valid or not. This routine
+ * also prompts the user.
+ *
+ * @returns {boolean} - true if this row represents a valid filter action.
+ */
+ validateAction() {
+ let filterActionString = this.getAttribute("value");
+ let actionTarget = this.children[1];
+ let actionTargetLabel =
+ actionTarget.ruleactiontargetElement &&
+ actionTarget.ruleactiontargetElement.children[0].value;
+ let errorString, customError;
+
+ switch (filterActionString) {
+ case "movemessage":
+ case "copymessage":
+ let msgFolder = actionTargetLabel
+ ? MailUtils.getOrCreateFolder(actionTargetLabel)
+ : null;
+ if (!msgFolder || !msgFolder.canFileMessages) {
+ errorString = "mustSelectFolder";
+ }
+ break;
+ case "forwardmessage":
+ if (
+ actionTargetLabel.length < 3 ||
+ actionTargetLabel.indexOf("@") < 1
+ ) {
+ errorString = "enterValidEmailAddress";
+ }
+ break;
+ case "replytomessage":
+ if (!actionTarget.ruleactiontargetElement.children[0].selectedItem) {
+ errorString = "pickTemplateToReplyWith";
+ }
+ break;
+ default:
+ // Locate the correct custom action, and check validity.
+ for (let i = 0; i < gCustomActions.length; i++) {
+ if (gCustomActions[i].id == filterActionString) {
+ customError = gCustomActions[i].validateActionValue(
+ actionTargetLabel,
+ gFilterList.folder,
+ gFilterType
+ );
+ break;
+ }
+ }
+ break;
+ }
+
+ errorString = errorString
+ ? gFilterBundle.getString(errorString)
+ : customError;
+ if (errorString) {
+ Services.prompt.alert(window, null, errorString);
+ }
+
+ return !errorString;
+ }
+
+ /**
+ * Create a new filter action, fill it in, and then append it to the filter.
+ *
+ * @param {object} aFilter - filter object to save.
+ */
+ saveToFilter(aFilter) {
+ let filterAction = aFilter.createAction();
+ let filterActionString = this.getAttribute("value");
+ filterAction.type = gFilterActionStrings.indexOf(filterActionString);
+ let actionTarget = this.children[1];
+ let actionItem = actionTarget.ruleactiontargetElement;
+ let nsMsgFilterAction = Ci.nsMsgFilterAction;
+ switch (filterAction.type) {
+ case nsMsgFilterAction.ChangePriority:
+ filterAction.priority = actionItem.children[0].getAttribute("value");
+ break;
+ case nsMsgFilterAction.MoveToFolder:
+ case nsMsgFilterAction.CopyToFolder:
+ filterAction.targetFolderUri = actionItem.children[0].value;
+ break;
+ case nsMsgFilterAction.JunkScore:
+ filterAction.junkScore = actionItem.children[0].value;
+ break;
+ case nsMsgFilterAction.Custom:
+ filterAction.customId = filterActionString;
+ // Fall through to set the value.
+ default:
+ if (actionItem && actionItem.children.length > 0) {
+ filterAction.strValue = actionItem.children[0].value;
+ }
+ break;
+ }
+ aFilter.appendAction(filterAction);
+ }
+
+ /**
+ * If we only have one row of actions, then disable the remove button for that row.
+ */
+ updateRemoveButton() {
+ this.mListBox.getItemAtIndex(0).mRemoveButton.disabled =
+ this.mListBox.getRowCount() == 1;
+ }
+
+ addRow() {
+ let listItem = document.createXULElement("richlistitem", {
+ is: "ruleaction-richlistitem",
+ });
+ listItem.classList.add("ruleaction");
+ listItem.setAttribute("onfocus", "this.storeFocus();");
+ this.mListBox.insertBefore(listItem, this.nextElementSibling);
+ this.mListBox.ensureElementIsVisible(listItem);
+
+ // Make sure the first remove button is enabled.
+ this.updateRemoveButton();
+ checkActionsReorder();
+ }
+
+ removeRow() {
+ // this.mListBox will fail after the row is removed, so save a reference.
+ let listBox = this.mListBox;
+ if (listBox.getRowCount() > 1) {
+ this.remove();
+ }
+ // Can't use 'this' as it is destroyed now.
+ listBox.getItemAtIndex(0).updateRemoveButton();
+ checkActionsReorder();
+ }
+
+ /**
+ * When this action row is focused, store its index in the parent richlistbox.
+ */
+ storeFocus() {
+ this.mListBox.setAttribute(
+ "focusedAction",
+ this.mListBox.getIndexOfItem(this)
+ );
+ }
+ }
+
+ customElements.define("ruleaction-richlistitem", MozRuleactionRichlistitem, {
+ extends: "richlistitem",
+ });
+}
diff --git a/comm/mailnews/search/content/viewLog.js b/comm/mailnews/search/content/viewLog.js
new file mode 100644
index 0000000000..6ac120b2cf
--- /dev/null
+++ b/comm/mailnews/search/content/viewLog.js
@@ -0,0 +1,38 @@
+/* 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 { MailE10SUtils } = ChromeUtils.import(
+ "resource:///modules/MailE10SUtils.jsm"
+);
+
+var gFilterList;
+var gLogFilters;
+var gLogView;
+
+window.addEventListener("DOMContentLoaded", onLoad);
+
+function onLoad() {
+ gFilterList = window.arguments[0].filterList;
+
+ gLogFilters = document.getElementById("logFilters");
+ gLogFilters.checked = gFilterList.loggingEnabled;
+
+ gLogView = document.getElementById("logView");
+
+ // for security, disable JS
+ gLogView.browsingContext.allowJavascript = false;
+
+ MailE10SUtils.loadURI(gLogView, gFilterList.logURL);
+}
+
+function toggleLogFilters() {
+ gFilterList.loggingEnabled = gLogFilters.checked;
+}
+
+function clearLog() {
+ gFilterList.clearLog();
+
+ // reload the newly truncated file
+ gLogView.reload();
+}
diff --git a/comm/mailnews/search/content/viewLog.xhtml b/comm/mailnews/search/content/viewLog.xhtml
new file mode 100644
index 0000000000..66d2671df9
--- /dev/null
+++ b/comm/mailnews/search/content/viewLog.xhtml
@@ -0,0 +1,65 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
+
+<!DOCTYPE html SYSTEM "chrome://messenger/locale/viewLog.dtd">
+
+<html
+ id="viewLogWindow"
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ windowtype="mailnews:filterlog"
+ width="600"
+ height="375"
+ persist="screenX screenY width height"
+ scrolling="false"
+>
+ <head>
+ <title>&viewLog.title;</title>
+ <script defer="defer" src="chrome://messenger/content/viewLog.js"></script>
+ </head>
+ <html:body
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ >
+ <dialog
+ buttons="accept"
+ buttonlabelaccept="&closeLog.label;"
+ buttonaccesskeyaccept="&closeLog.accesskey;"
+ >
+ <vbox flex="1">
+ <description>&viewLogInfo.text;</description>
+ <hbox>
+ <checkbox
+ id="logFilters"
+ label="&enableLog.label;"
+ accesskey="&enableLog.accesskey;"
+ oncommand="toggleLogFilters();"
+ />
+ <spacer flex="1" />
+ <button
+ label="&clearLog.label;"
+ accesskey="&clearLog.accesskey;"
+ oncommand="clearLog();"
+ />
+ </hbox>
+ <separator class="thin" />
+ <hbox flex="1">
+ <browser
+ id="logView"
+ class="inset"
+ type="content"
+ disablehistory="true"
+ disablesecurity="true"
+ src="about:blank"
+ autofind="false"
+ flex="1"
+ />
+ </hbox>
+ </vbox>
+ </dialog>
+ </html:body>
+</html>
diff --git a/comm/mailnews/search/public/moz.build b/comm/mailnews/search/public/moz.build
new file mode 100644
index 0000000000..39a41a1d2a
--- /dev/null
+++ b/comm/mailnews/search/public/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/.
+
+XPIDL_SOURCES += [
+ "nsIMsgFilter.idl",
+ "nsIMsgFilterCustomAction.idl",
+ "nsIMsgFilterHitNotify.idl",
+ "nsIMsgFilterList.idl",
+ "nsIMsgFilterPlugin.idl",
+ "nsIMsgFilterService.idl",
+ "nsIMsgOperationListener.idl",
+ "nsIMsgSearchAdapter.idl",
+ "nsIMsgSearchCustomTerm.idl",
+ "nsIMsgSearchNotify.idl",
+ "nsIMsgSearchScopeTerm.idl",
+ "nsIMsgSearchSession.idl",
+ "nsIMsgSearchTerm.idl",
+ "nsIMsgSearchValidityManager.idl",
+ "nsIMsgSearchValidityTable.idl",
+ "nsIMsgSearchValue.idl",
+ "nsIMsgTraitService.idl",
+ "nsMsgFilterCore.idl",
+ "nsMsgSearchCore.idl",
+]
+
+XPIDL_MODULE = "msgsearch"
+
+EXPORTS += [
+ "nsMsgBodyHandler.h",
+ "nsMsgResultElement.h",
+ "nsMsgSearchAdapter.h",
+ "nsMsgSearchBoolExpression.h",
+ "nsMsgSearchScopeTerm.h",
+ "nsMsgSearchTerm.h",
+]
diff --git a/comm/mailnews/search/public/nsIMsgFilter.idl b/comm/mailnews/search/public/nsIMsgFilter.idl
new file mode 100644
index 0000000000..6cf65c774e
--- /dev/null
+++ b/comm/mailnews/search/public/nsIMsgFilter.idl
@@ -0,0 +1,124 @@
+/* -*- Mode: IDL; 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/. */
+
+
+#include "nsISupports.idl"
+#include "nsMsgFilterCore.idl"
+
+interface nsIOutputStream;
+interface nsIMsgFilterCustomAction;
+interface nsIMsgFilterList;
+interface nsIMsgSearchScopeTerm;
+interface nsIMsgSearchValue;
+interface nsIMsgSearchTerm;
+
+[scriptable, uuid(36d2748e-9246-44f3-bb74-46cbb0b8c23a)]
+interface nsIMsgRuleAction : nsISupports {
+
+ attribute nsMsgRuleActionType type;
+
+ // target priority.. throws an exception if the action is not priority
+ attribute nsMsgPriorityValue priority;
+
+ // target folder.. throws an exception if the action is not move to folder
+ attribute AUTF8String targetFolderUri;
+
+ attribute long junkScore;
+
+ attribute AUTF8String strValue;
+
+ // action id if type is Custom
+ attribute ACString customId;
+
+ // custom action associated with customId
+ // (which must be set prior to reading this attribute)
+ readonly attribute nsIMsgFilterCustomAction customAction;
+
+};
+
+[scriptable, uuid(d304fcfc-b588-11e4-981c-770e1e5d46b0)]
+interface nsIMsgFilter : nsISupports {
+ attribute nsMsgFilterTypeType filterType;
+ /**
+ * some filters are "temporary". For example, the filters we create when the user
+ * filters return receipts to the Sent folder.
+ * we don't show temporary filters in the UI
+ * and we don't write them to disk.
+ */
+ attribute boolean temporary;
+ attribute boolean enabled;
+ attribute AString filterName;
+ attribute ACString filterDesc;
+ attribute ACString unparsedBuffer; //holds the entire filter if we don't know how to handle it
+ attribute boolean unparseable; //whether we could parse the filter or not
+
+ attribute nsIMsgFilterList filterList; // owning filter list
+
+ void AddTerm(in nsMsgSearchAttribValue attrib,
+ in nsMsgSearchOpValue op,
+ in nsIMsgSearchValue value,
+ in boolean BooleanAND,
+ in ACString arbitraryHeader);
+
+ void GetTerm(in long termIndex,
+ out nsMsgSearchAttribValue attrib,
+ out nsMsgSearchOpValue op,
+ out nsIMsgSearchValue value, // bad! using shared structure
+ out boolean BooleanAND,
+ out ACString arbitraryHeader);
+
+ void appendTerm(in nsIMsgSearchTerm term);
+
+ nsIMsgSearchTerm createTerm();
+
+ attribute Array<nsIMsgSearchTerm> searchTerms;
+
+ attribute nsIMsgSearchScopeTerm scope;
+
+ boolean MatchHdr(in nsIMsgDBHdr msgHdr, in nsIMsgFolder folder,
+ in nsIMsgDatabase db,
+ in ACString headers); // null-separated list of headers
+
+
+ /*
+ * Report that Rule was matched and executed when filter logging is enabled.
+ *
+ * @param aFilterAction The filter rule that was invoked.
+ * @param aHeader The header information of the message acted on by
+ * the filter.
+ */
+ void logRuleHit(in nsIMsgRuleAction aFilterAction,
+ in nsIMsgDBHdr aHeader);
+
+ /* Report that filtering failed for some reason when filter logging is enabled.
+ *
+ * @param aFilterAction Filter rule that was invoked.
+ * @param aHeader Header of the message acted on by the filter.
+ * @param aRcode Error code returned by low-level routine that
+ * led to the filter failure.
+ * @param aErrmsg Error message
+ */
+ void logRuleHitFail(in nsIMsgRuleAction aFilterAction,
+ in nsIMsgDBHdr aHeader,
+ in nsresult aRcode,
+ in AUTF8String aErrmsg);
+
+ nsIMsgRuleAction createAction();
+
+ nsIMsgRuleAction getActionAt(in unsigned long aIndex);
+
+ long getActionIndex(in nsIMsgRuleAction aAction);
+
+ void appendAction(in nsIMsgRuleAction action);
+
+ readonly attribute unsigned long actionCount;
+
+ void clearActionList();
+
+ // Returns the action list in the order it will be really executed in.
+ readonly attribute Array<nsIMsgRuleAction> sortedActionList;
+
+ void SaveToTextFile(in nsIOutputStream aStream);
+};
diff --git a/comm/mailnews/search/public/nsIMsgFilterCustomAction.idl b/comm/mailnews/search/public/nsIMsgFilterCustomAction.idl
new file mode 100644
index 0000000000..6e0f15cb09
--- /dev/null
+++ b/comm/mailnews/search/public/nsIMsgFilterCustomAction.idl
@@ -0,0 +1,88 @@
+/* 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 "nsMsgFilterCore.idl"
+
+interface nsIMsgCopyServiceListener;
+interface nsIMsgWindow;
+interface nsIMsgDBHdr;
+
+/**
+ * describes a custom action added to a message filter
+ */
+[scriptable,uuid(4699C41E-3671-436e-B6AE-4FD8106747E4)]
+interface nsIMsgFilterCustomAction : nsISupports
+{
+ /* globally unique string to identify this filter action.
+ * recommended form: ExtensionName@example.com#ActionName
+ */
+ readonly attribute ACString id;
+
+ /* action name to display in action list. This should be localized. */
+ readonly attribute AString name;
+
+ /**
+ * Is this custom action valid for a particular filter type?
+ *
+ * @param type the filter type
+ * @param scope the search scope
+ *
+ * @return true if valid
+ */
+ boolean isValidForType(in nsMsgFilterTypeType type, in nsMsgSearchScopeValue scope);
+
+ /**
+ * After the user inputs a particular action value for the action, determine
+ * if that value is valid.
+ *
+ * @param actionValue The value entered.
+ * @param actionFolder Folder in the filter list
+ * @param filterType Filter Type (Manual, OfflineMail, etc.)
+ *
+ * @return errorMessage A localized message to display if invalid
+ * Set to null if the actionValue is valid
+ */
+ AUTF8String validateActionValue(in AUTF8String actionValue,
+ in nsIMsgFolder actionFolder,
+ in nsMsgFilterTypeType filterType);
+
+ /* allow duplicate actions in the same filter list? Default No. */
+ attribute boolean allowDuplicates;
+
+ /*
+ * The custom action itself
+ *
+ * Generally for the applyAction method, folder-based methods give correct
+ * results and are preferred if available. Otherwise, be careful
+ * that the action does correct notifications to maintain counts, and correct
+ * manipulations of both IMAP and local non-database storage of message
+ * metadata.
+ */
+
+ /**
+ * Apply the custom action to an array of messages
+ *
+ * @param msgHdrs array of nsIMsgDBHdr objects of messages
+ * @param actionValue user-set value to use in the action
+ * @param copyListener calling method (filterType Manual only)
+ * @param filterType type of filter being applied
+ * @param msgWindow message window
+ */
+
+ void applyAction(in Array<nsIMsgDBHdr> msgHdrs,
+ in AUTF8String actionValue,
+ in nsIMsgCopyServiceListener copyListener,
+ in nsMsgFilterTypeType filterType,
+ in nsIMsgWindow msgWindow);
+
+ /* does this action start an async action? If so, a copy listener must
+ * be used to continue filter processing after the action. This only
+ * applies to after-the-fact (manual) filters. Call OnStopCopy when done
+ * using the copyListener to continue.
+ */
+ readonly attribute boolean isAsync;
+
+ /// Does this action need the message body?
+ readonly attribute boolean needsBody;
+};
diff --git a/comm/mailnews/search/public/nsIMsgFilterHitNotify.idl b/comm/mailnews/search/public/nsIMsgFilterHitNotify.idl
new file mode 100644
index 0000000000..c0bea47db1
--- /dev/null
+++ b/comm/mailnews/search/public/nsIMsgFilterHitNotify.idl
@@ -0,0 +1,26 @@
+/* -*- Mode: IDL; 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/. */
+
+#include "nsISupports.idl"
+
+interface nsIMsgFilter;
+interface nsIMsgWindow;
+
+///////////////////////////////////////////////////////////////////////////////
+// nsIMsgFilterHitNotify is an interface designed to make evaluating filters
+// easier. Clients typically open a filter list and ask the filter list to
+// evaluate the filters for a particular message, and pass in an
+// interface pointer to be notified of hits. The filter list will call the
+// ApplyFilterHit method on the interface pointer in case of hits, along with
+// the desired action and value.
+// return value is used to indicate whether the
+// filter list should continue trying to apply filters or not.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+[scriptable, uuid(c9f15174-1f3f-11d3-a51b-0060b0fc04b7)]
+interface nsIMsgFilterHitNotify : nsISupports {
+ boolean applyFilterHit(in nsIMsgFilter filter, in nsIMsgWindow msgWindow);
+};
diff --git a/comm/mailnews/search/public/nsIMsgFilterList.idl b/comm/mailnews/search/public/nsIMsgFilterList.idl
new file mode 100644
index 0000000000..fa294d5063
--- /dev/null
+++ b/comm/mailnews/search/public/nsIMsgFilterList.idl
@@ -0,0 +1,115 @@
+/* -*- 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/. */
+
+#include "nsISupports.idl"
+#include "nsIMsgFilterHitNotify.idl"
+#include "nsMsgFilterCore.idl"
+
+interface nsIFile;
+interface nsIOutputStream;
+interface nsIMsgFilter;
+interface nsIMsgFolder;
+
+///////////////////////////////////////////////////////////////////////////////
+// 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...
+//
+///////////////////////////////////////////////////////////////////////////////
+
+typedef long nsMsgFilterFileAttribValue;
+
+[scriptable, uuid(5d0ec03e-7e2f-49e9-b58a-b274c85f279e)]
+interface nsIMsgFilterList : nsISupports {
+
+ const nsMsgFilterFileAttribValue attribNone = 0;
+ const nsMsgFilterFileAttribValue attribVersion = 1;
+ const nsMsgFilterFileAttribValue attribLogging = 2;
+ const nsMsgFilterFileAttribValue attribName = 3;
+ const nsMsgFilterFileAttribValue attribEnabled = 4;
+ const nsMsgFilterFileAttribValue attribDescription = 5;
+ const nsMsgFilterFileAttribValue attribType = 6;
+ const nsMsgFilterFileAttribValue attribScriptFile = 7;
+ const nsMsgFilterFileAttribValue attribAction = 8;
+ const nsMsgFilterFileAttribValue attribActionValue = 9;
+ const nsMsgFilterFileAttribValue attribCondition = 10;
+ const nsMsgFilterFileAttribValue attribCustomId = 11;
+
+ /// Unique text identifier of this filter list.
+ readonly attribute ACString listId;
+ attribute nsIMsgFolder folder;
+ readonly attribute short version;
+ readonly attribute ACString arbitraryHeaders;
+ readonly attribute boolean shouldDownloadAllHeaders;
+ readonly attribute unsigned long filterCount;
+ nsIMsgFilter getFilterAt(in unsigned long filterIndex);
+ nsIMsgFilter getFilterNamed(in AString filterName);
+
+ void setFilterAt(in unsigned long filterIndex, in nsIMsgFilter filter);
+ void removeFilter(in nsIMsgFilter filter);
+ void removeFilterAt(in unsigned long filterIndex);
+
+ void moveFilterAt(in unsigned long filterIndex,
+ in nsMsgFilterMotionValue motion);
+ void moveFilter(in nsIMsgFilter filter,
+ in nsMsgFilterMotionValue motion);
+
+ void insertFilterAt(in unsigned long filterIndex, in nsIMsgFilter filter);
+
+ nsIMsgFilter createFilter(in AString name);
+
+ void saveToFile(in nsIOutputStream stream);
+
+ void parseCondition(in nsIMsgFilter aFilter, in string condition);
+ // this is temporary so that we can save the filterlist to disk
+ // without knowing where the filters were read from initially
+ // (such as the filter list dialog)
+ attribute nsIFile defaultFile;
+ void saveToDefaultFile();
+
+ void applyFiltersToHdr(in nsMsgFilterTypeType filterType,
+ in nsIMsgDBHdr msgHdr,
+ in nsIMsgFolder folder,
+ in nsIMsgDatabase db,
+ in ACString headers, // null-separated list of headers
+ in nsIMsgFilterHitNotify listener,
+ in nsIMsgWindow msgWindow);
+
+ // IO routines, used by filter object filing code.
+ void writeIntAttr(in nsMsgFilterFileAttribValue attrib, in long value, in nsIOutputStream stream);
+ void writeStrAttr(in nsMsgFilterFileAttribValue attrib, in string value, in nsIOutputStream stream);
+ void writeWstrAttr(in nsMsgFilterFileAttribValue attrib, in wstring value, in nsIOutputStream stream);
+ void writeBoolAttr(in nsMsgFilterFileAttribValue attrib, in boolean value, in nsIOutputStream stream);
+ boolean matchOrChangeFilterTarget(in AUTF8String oldUri, in AUTF8String newUri, in boolean caseInsensitive);
+
+ /**
+ * Turn filter logging on or off. Turning logging off will close any
+ * currently-open open logfile.
+ */
+ attribute boolean loggingEnabled;
+ /**
+ * The log will be written via logStream. This may be null if
+ * loggingEnabled is false or if there is some problem with the logging.
+ */
+ attribute nsIOutputStream logStream;
+ readonly attribute ACString logURL;
+ void clearLog();
+ void flushLogIfNecessary();
+ /**
+ * Push a message to the filter log file, adding a timestamp.
+ *
+ * @param message The message text to log.
+ * @param filter Optional filter object that reports the message.
+ */
+ void logFilterMessage(in AString message, [optional] in nsIMsgFilter filter);
+};
+
+
+/* these longs are all actually of type nsMsgFilterMotionValue */
+[scriptable, uuid(d067b528-304e-11d3-a0e1-00a0c900d445)]
+interface nsMsgFilterMotion : nsISupports {
+ const long up = 0;
+ const long down = 1;
+};
diff --git a/comm/mailnews/search/public/nsIMsgFilterPlugin.idl b/comm/mailnews/search/public/nsIMsgFilterPlugin.idl
new file mode 100644
index 0000000000..93934a364a
--- /dev/null
+++ b/comm/mailnews/search/public/nsIMsgFilterPlugin.idl
@@ -0,0 +1,331 @@
+/* -*- 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/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+interface nsIMsgWindow;
+interface nsIFile;
+
+/**
+ * This interface is still very much under development, and is not yet stable.
+ */
+
+[scriptable, uuid(e2e56690-a676-11d6-80c9-00008646b737)]
+interface nsIMsgFilterPlugin : nsISupports
+{
+ /**
+ * Do any necessary cleanup: flush and close any open files, etc.
+ */
+ void shutdown();
+
+ /**
+ * Some protocols (ie IMAP) can, as an optimization, avoid
+ * downloading all message header lines. If your plugin doesn't need
+ * any more than the minimal set, it can return false for this attribute.
+ */
+ readonly attribute boolean shouldDownloadAllHeaders;
+
+};
+
+/*
+ * These interfaces typically implement a Bayesian classifier of messages.
+ *
+ * Two sets of interfaces may be used: the older junk-only interfaces, and
+ * the newer trait-oriented interfaces that treat junk classification as
+ * one of a set of classifications to accomplish.
+ */
+
+[scriptable, uuid(b15a0f9c-df07-4af0-9ba8-80dca68ac35d)]
+interface nsIJunkMailClassificationListener : nsISupports
+{
+ /**
+ * Inform a listener of a message's classification as junk. At the end
+ * of a batch of classifications, signify end of batch by calling with
+ * null aMsgURI (other parameters are don't care)
+ *
+ * @param aMsgURI URI of the message that was classified.
+ * @param aClassification classification of message as UNCLASSIFIED, GOOD,
+ * or JUNK.
+ * @param aJunkPercent indicator of degree of uncertainty, with 100 being
+ * probably junk, and 0 probably good
+ */
+ void onMessageClassified(in AUTF8String aMsgURI,
+ in nsMsgJunkStatus aClassification,
+ in uint32_t aJunkPercent);
+};
+
+[scriptable, uuid(AF247D07-72F0-482d-9EAB-5A786407AA4C)]
+interface nsIMsgTraitClassificationListener : nsISupports
+{
+ /**
+ * Inform a listener of a message's match to traits. The list
+ * of traits being matched is in aTraits. Corresponding
+ * indicator of match (percent) is in aPercents. At the end
+ * of a batch of classifications, signify end of batch by calling with
+ * null aMsgURI (other parameters are don't care)
+ *
+ * @param aMsgURI URI of the message that was classified
+ * @param aTraits array of matched trait ids
+ * @param aPercents array of percent match (0 is unmatched, 100 is fully
+ * matched) of the trait with the corresponding array
+ * index in aTraits
+ */
+ void onMessageTraitsClassified(in AUTF8String aMsgURI,
+ in Array<unsigned long> aTraits,
+ in Array<unsigned long> aPercents);
+};
+
+[scriptable, uuid(12667532-88D1-44a7-AD48-F73719BE5C92)]
+interface nsIMsgTraitDetailListener : nsISupports
+{
+ /**
+ * Inform a listener of details of a message's match to traits.
+ * This returns the tokens that were used in the calculation,
+ * the calculated percent probability that each token matches the trait,
+ * and a running estimate (starting with the strongest tokens) of the
+ * combined total probability that a message matches the trait, when
+ * only tokens stronger than the current token are used.
+ *
+ * @param aMsgURI URI of the message that was classified
+ * @param aProTrait trait id of pro trait for the calculation
+ * @param tokenStrings the string for a particular token
+ * @param tokenPercents calculated probability that a message with that token
+ * matches the trait
+ * @param runningPercents calculated probability that the message matches the
+ * trait, accounting for this token and all stronger tokens.
+ */
+ void onMessageTraitDetails(in AUTF8String aMsgUri,
+ in unsigned long aProTrait,
+ in Array<AString> tokenStrings,
+ in Array<unsigned long> tokenPercents,
+ in Array<unsigned long> runningPercents);
+};
+
+[scriptable, uuid(8EA5BBCA-F735-4d43-8541-D203D8E2FF2F)]
+interface nsIJunkMailPlugin : nsIMsgFilterPlugin
+{
+ /**
+ * Message classifications.
+ */
+ const nsMsgJunkStatus UNCLASSIFIED = 0;
+ const nsMsgJunkStatus GOOD = 1;
+ const nsMsgJunkStatus JUNK = 2;
+
+ /**
+ * Message junk score constants. Junkscore can only be one of these two
+ * values (or not set).
+ */
+ const nsMsgJunkScore IS_SPAM_SCORE = 100; // junk
+ const nsMsgJunkScore IS_HAM_SCORE = 0; // not junk
+
+ /**
+ * Trait ids for junk analysis. These values are fixed to ensure
+ * backwards compatibility with existing junk-oriented classification
+ * code.
+ */
+
+ const unsigned long GOOD_TRAIT = 1; // good
+ const unsigned long JUNK_TRAIT = 2; // junk
+
+ /**
+ * Given a message URI, determine what its current classification is
+ * according to the current training set.
+ */
+ void classifyMessage(in AUTF8String aMsgURI, in nsIMsgWindow aMsgWindow,
+ in nsIJunkMailClassificationListener aListener);
+
+ void classifyMessages(in Array<AUTF8String> aMsgURIs,
+ in nsIMsgWindow aMsgWindow,
+ in nsIJunkMailClassificationListener aListener);
+
+ /**
+ * Given a message URI, evaluate its relative match to a list of
+ * traits according to the current training set.
+ *
+ * @param aMsgURI URI of the message to be evaluated
+ * @param aProTraits array of trait ids for trained messages that
+ * match the tested trait (for example,
+ * JUNK_TRAIT if testing for junk)
+ * @param aAntiTraits array of trait ids for trained messages that
+ * do not match the tested trait (for example,
+ * GOOD_TRAIT if testing for junk)
+ * @param aTraitListener trait-oriented callback listener (may be null)
+ * @param aMsgWindow current message window (may be null)
+ * @param aJunkListener junk-oriented callback listener (may be null)
+ */
+
+ void classifyTraitsInMessage(
+ in AUTF8String aMsgURI,
+ in Array<unsigned long> aProTraits,
+ in Array<unsigned long> aAntiTraits,
+ in nsIMsgTraitClassificationListener aTraitListener,
+ [optional] in nsIMsgWindow aMsgWindow,
+ [optional] in nsIJunkMailClassificationListener aJunkListener);
+
+ /**
+ * Given an array of message URIs, evaluate their relative match to a
+ * list of traits according to the current training set.
+ *
+ * @param aMsgURIs array of URIs of the messages to be evaluated
+ * @param aProTraits array of trait ids for trained messages that
+ * match the tested trait (for example,
+ * JUNK_TRAIT if testing for junk)
+ * @param aAntiTraits array of trait ids for trained messages that
+ * do not match the tested trait (for example,
+ * GOOD_TRAIT if testing for junk)
+ * @param aTraitListener trait-oriented callback listener (may be null)
+ * @param aMsgWindow current message window (may be null)
+ * @param aJunkListener junk-oriented callback listener (may be null)
+ */
+
+ void classifyTraitsInMessages(
+ in Array<AUTF8String> aMsgURIs,
+ in Array<unsigned long> aProTraits,
+ in Array<unsigned long> aAntiTraits,
+ in nsIMsgTraitClassificationListener aTraitListener,
+ [optional] in nsIMsgWindow aMsgWindow,
+ [optional] in nsIJunkMailClassificationListener aJunkListener);
+
+ /**
+ * Called when a user forces the classification of a message. Should
+ * cause the training set to be updated appropriately.
+ *
+ * @arg aMsgURI URI of the message to be classified
+ * @arg aOldUserClassification Was it previous manually classified
+ * by the user? If so, how?
+ * @arg aNewClassification New manual classification.
+ * @arg aListener Callback (may be null)
+ */
+ void setMessageClassification(
+ in AUTF8String aMsgURI, in nsMsgJunkStatus aOldUserClassification,
+ in nsMsgJunkStatus aNewClassification,
+ in nsIMsgWindow aMsgWindow,
+ in nsIJunkMailClassificationListener aListener);
+
+ /**
+ * Called when a user forces a change in the classification of a message.
+ * Should cause the training set to be updated appropriately.
+ *
+ * @param aMsgURI URI of the message to be classified
+ * @param aOldTraits array of trait IDs of the old
+ * message classification(s), if any
+ * @param aNewTraits array of trait IDs of the new
+ * message classification(s), if any
+ * @param aTraitListener trait-oriented listener (may be null)
+ * @param aMsgWindow current message window (may be null)
+ * @param aJunkListener junk-oriented listener (may be null)
+ */
+ void setMsgTraitClassification(
+ in AUTF8String aMsgURI,
+ in Array<unsigned long> aOldTraits,
+ in Array<unsigned long> aNewTraits,
+ [optional] in nsIMsgTraitClassificationListener aTraitListener,
+ [optional] in nsIMsgWindow aMsgWindow,
+ [optional] in nsIJunkMailClassificationListener aJunkListener);
+
+ readonly attribute boolean userHasClassified;
+
+ /** Removes the training file and clears out any in memory training tokens.
+ User must retrain after doing this.
+ **/
+ void resetTrainingData();
+
+ /**
+ * Given a message URI, return a list of tokens and their contribution to
+ * the analysis of a message's match to a trait according to the
+ * current training set.
+ *
+ * @param aMsgURI URI of the message to be evaluated
+ * @param aProTrait trait id for trained messages that match the
+ * tested trait (for example, JUNK_TRAIT if testing
+ * for junk)
+ * @param aAntiTrait trait id for trained messages that do not match
+ * the tested trait (for example, GOOD_TRAIT
+ * if testing for junk)
+ * @param aListener callback listener for results
+ * @param aMsgWindow current message window (may be null)
+ */
+ void detailMessage(
+ in AUTF8String aMsgURI,
+ in unsigned long aProTrait,
+ in unsigned long aAntiTrait,
+ in nsIMsgTraitDetailListener aListener,
+ [optional] in nsIMsgWindow aMsgWindow);
+
+};
+
+/**
+ * The nsIMsgCorpus interface manages a corpus of mail data used for
+ * statistical analysis of messages.
+ */
+[scriptable, uuid(70BAD26F-DFD4-41bd-8FAB-4C09B9C1E845)]
+interface nsIMsgCorpus : nsISupports
+{
+ /**
+ * Clear the corpus data for a trait id.
+ *
+ * @param aTrait trait id
+ */
+ void clearTrait(in unsigned long aTrait);
+
+ /**
+ * Update corpus data from a file.
+ * Uses the parallel arrays aFromTraits and aToTraits. These arrays allow
+ * conversion of the trait id stored in the file (which may be originated
+ * externally) to the trait id used in the local corpus (which is defined
+ * locally using nsIMsgTraitService, and mapped by that interface to a
+ * globally unique trait id string).
+ *
+ * @param aFile the file with the data, in the format:
+ *
+ * Format of the trait file for version 1:
+ * [0xFCA93601] (the 01 is the version)
+ * for each trait to write:
+ * [id of trait to write] (0 means end of list)
+ * [number of messages per trait]
+ * for each token with non-zero count
+ * [count]
+ * [length of word]word
+ *
+ * @param aIsAdd should the data be added, or removed? True if
+ * adding, false if removing.
+ *
+ * @param aFromTraits array of trait ids used in aFile. If aFile contains
+ * trait ids that are not in this array, they are not
+ * remapped, but assumed to be local trait ids.
+ *
+ * @param aToTraits array of trait ids, corresponding to elements of
+ * aFromTraits, that represent the local trait ids to
+ * be used in storing data from aFile into the local corpus.
+ */
+ void updateData(in nsIFile aFile, in boolean aIsAdd,
+ [optional] in Array<unsigned long> aFromTraits,
+ [optional] in Array<unsigned long> aToTraits);
+
+ /**
+ * Get the corpus count for a token as a string.
+ *
+ * @param aWord string of characters representing the token
+ * @param aTrait trait id
+ *
+ * @return count of that token in the corpus
+ *
+ */
+ unsigned long getTokenCount(in AUTF8String aWord, in unsigned long aTrait);
+
+ /**
+ * Gives information on token and message count information in the
+ * training data corpus.
+ *
+ * @param aTrait trait id (may be null)
+ * @param aMessageCount count of messages that have been trained with aTrait
+ *
+ * @return token count for all traits
+ */
+
+ unsigned long corpusCounts(in unsigned long aTrait, out unsigned long aMessageCount);
+};
diff --git a/comm/mailnews/search/public/nsIMsgFilterService.idl b/comm/mailnews/search/public/nsIMsgFilterService.idl
new file mode 100644
index 0000000000..439dd40f93
--- /dev/null
+++ b/comm/mailnews/search/public/nsIMsgFilterService.idl
@@ -0,0 +1,102 @@
+/* -*- Mode: IDL; 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/. */
+
+#include "nsISupports.idl"
+#include "nsMsgFilterCore.idl"
+
+interface nsIMsgFilterList;
+interface nsIMsgWindow;
+interface nsIMsgFilterCustomAction;
+interface nsIFile;
+interface nsIMsgFolder;
+interface nsIMsgSearchCustomTerm;
+interface nsIMsgOperationListener;
+
+[scriptable, uuid(78a74023-1692-4567-8d72-9ca58fbbd427)]
+interface nsIMsgFilterService : nsISupports {
+
+ nsIMsgFilterList OpenFilterList(in nsIFile filterFile, in nsIMsgFolder rootFolder, in nsIMsgWindow msgWindow);
+ void CloseFilterList(in nsIMsgFilterList filterList);
+
+ void SaveFilterList(in nsIMsgFilterList filterList,
+ in nsIFile filterFile);
+
+ void CancelFilterList(in nsIMsgFilterList filterList);
+ nsIMsgFilterList getTempFilterList(in nsIMsgFolder aFolder);
+ void applyFiltersToFolders(in nsIMsgFilterList aFilterList,
+ in Array<nsIMsgFolder> aFolders,
+ in nsIMsgWindow aMsgWindow,
+ [optional] in nsIMsgOperationListener aCallback);
+
+ /**
+ * Apply filters to a specific list of messages in a folder.
+ * @param aFilterType The type of filter to match against
+ * @param aMsgHdrList The list of message headers (nsIMsgDBHdr objects)
+ * @param aFolder The folder the messages belong to
+ * @param aMsgWindow A UI window for attaching progress/dialogs
+ * @param aCallback A listener that gets notified of any filtering error
+ */
+ void applyFilters(in nsMsgFilterTypeType aFilterType,
+ in Array<nsIMsgDBHdr> aMsgHdrList,
+ in nsIMsgFolder aFolder,
+ in nsIMsgWindow aMsgWindow,
+ [optional] in nsIMsgOperationListener aCallback);
+
+ /**
+ * Add a custom filter action.
+ *
+ * @param aAction the custom action to add
+ */
+ void addCustomAction(in nsIMsgFilterCustomAction aAction);
+
+ /**
+ * get the list of custom actions
+ *
+ * @return an array of nsIMsgFilterCustomAction objects
+ */
+ Array<nsIMsgFilterCustomAction> getCustomActions();
+
+ /**
+ * Lookup a custom action given its id.
+ *
+ * @param id unique identifier for a particular custom action
+ *
+ * @return the custom action, or null if not found
+ */
+ nsIMsgFilterCustomAction getCustomAction(in ACString id);
+
+ /**
+ * Add a custom search term.
+ *
+ * @param aTerm the custom term to add
+ */
+ void addCustomTerm(in nsIMsgSearchCustomTerm aTerm);
+
+ /**
+ * get the list of custom search terms
+ *
+ * @return an array of nsIMsgSearchCustomTerm objects
+ */
+ Array<nsIMsgSearchCustomTerm> getCustomTerms();
+
+ /**
+ * Lookup a custom search term given its id.
+ *
+ * @param id unique identifier for a particular custom search term
+ *
+ * @return the custom search term, or null if not found
+ */
+ nsIMsgSearchCustomTerm getCustomTerm(in ACString id);
+
+ /**
+ * Translate the filter type flag into human readable type names.
+ * In case of multiple flag they are delimited by '&'.
+ *
+ * @param filterType nsMsgFilterType flags of filter type
+ *
+ * @return A string describing the filter type.
+ */
+ ACString filterTypeName(in nsMsgFilterTypeType filterType);
+};
diff --git a/comm/mailnews/search/public/nsIMsgOperationListener.idl b/comm/mailnews/search/public/nsIMsgOperationListener.idl
new file mode 100644
index 0000000000..30751cd5c3
--- /dev/null
+++ b/comm/mailnews/search/public/nsIMsgOperationListener.idl
@@ -0,0 +1,17 @@
+/* -*- Mode: IDL; 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/. */
+
+#include "nsISupports.idl"
+
+// Listener used to notify when an operation has completed.
+[scriptable, uuid(bdaef6ff-0909-435b-8fcd-76525dd2364c)]
+interface nsIMsgOperationListener : nsISupports {
+ /**
+ * Called when the operation stops (possibly with errors)
+ *
+ * @param aStatus Success or failure of the operation
+ */
+ void onStopOperation(in nsresult aStatus);
+};
diff --git a/comm/mailnews/search/public/nsIMsgSearchAdapter.idl b/comm/mailnews/search/public/nsIMsgSearchAdapter.idl
new file mode 100644
index 0000000000..9784fdda79
--- /dev/null
+++ b/comm/mailnews/search/public/nsIMsgSearchAdapter.idl
@@ -0,0 +1,41 @@
+/* -*- 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/. */
+
+
+#include "nsISupports.idl"
+#include "nsIMsgSearchScopeTerm.idl"
+
+[ptr] native nsMsgResultElement(nsMsgResultElement);
+
+%{C++
+class nsMsgResultElement;
+%}
+
+[scriptable, uuid(0b09078b-e0cd-440a-afee-01f45808ee74)]
+interface nsIMsgSearchAdapter : nsISupports {
+ void ValidateTerms();
+ void Search(out boolean done);
+ void SendUrl();
+ void CurrentUrlDone(in nsresult exitCode);
+
+ void AddHit(in nsMsgKey key);
+ void AddResultElement(in nsIMsgDBHdr aHdr);
+
+ [noscript] void OpenResultElement(in nsMsgResultElement element);
+ [noscript] void ModifyResultElement(in nsMsgResultElement element,
+ in nsMsgSearchValue value);
+
+ readonly attribute string encoding;
+
+ [noscript] nsIMsgFolder FindTargetFolder([const] in nsMsgResultElement
+ element);
+ void Abort();
+ void getSearchCharsets(out AString srcCharset, out AString destCharset);
+ /*
+ * Clear the saved scope reference. This is used when deleting scope, which is not
+ * reference counted in nsMsgSearchSession
+ */
+ void clearScope();
+};
diff --git a/comm/mailnews/search/public/nsIMsgSearchCustomTerm.idl b/comm/mailnews/search/public/nsIMsgSearchCustomTerm.idl
new file mode 100644
index 0000000000..b2e3c024cd
--- /dev/null
+++ b/comm/mailnews/search/public/nsIMsgSearchCustomTerm.idl
@@ -0,0 +1,75 @@
+/* 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 "nsMsgSearchCore.idl"
+
+/**
+ * describes a custom term added to a message search or filter
+ */
+[scriptable,uuid(925DB5AA-21AF-494c-8652-984BC7BAD13A)]
+interface nsIMsgSearchCustomTerm : nsISupports
+{
+ /**
+ * globally unique string to identify this search term.
+ * recommended form: ExtensionName@example.com#TermName
+ * Commas and quotes are not allowed, the id must not
+ * parse to an integer, and names of standard search
+ * attributes in SearchAttribEntryTable in nsMsgSearchTerm.cpp
+ * are not allowed.
+ */
+ readonly attribute ACString id;
+
+ /// name to display in term list. This should be localized. */
+ readonly attribute AString name;
+
+ /// Does this term need the message body?
+ readonly attribute boolean needsBody;
+
+ /**
+ * Is this custom term enabled?
+ *
+ * @param scope search scope (nsMsgSearchScope)
+ * @param op search operator (nsMsgSearchOp). If null, determine
+ * if term is available for any operator.
+ *
+ * @return true if enabled
+ */
+ boolean getEnabled(in nsMsgSearchScopeValue scope,
+ in nsMsgSearchOpValue op);
+
+ /**
+ * Is this custom term available?
+ *
+ * @param scope search scope (nsMsgSearchScope)
+ * @param op search operator (nsMsgSearchOp). If null, determine
+ * if term is available for any operator.
+ *
+ * @return true if available
+ */
+ boolean getAvailable(in nsMsgSearchScopeValue scope,
+ in nsMsgSearchOpValue op);
+
+ /**
+ * List the valid operators for this term.
+ *
+ * @param scope search scope (nsMsgSearchScope)
+ *
+ * @return array of operators
+ */
+ Array<nsMsgSearchOpValue> getAvailableOperators(in nsMsgSearchScopeValue scope);
+
+ /**
+ * Apply the custom search term to a message
+ *
+ * @param msgHdr header database reference representing the message
+ * @param searchValue user-set value to use in the search
+ * @param searchOp search operator (Contains, IsHigherThan, etc.)
+ *
+ * @return true if the term matches the message, else false
+ */
+
+ boolean match(in nsIMsgDBHdr msgHdr,
+ in AUTF8String searchValue,
+ in nsMsgSearchOpValue searchOp);
+};
diff --git a/comm/mailnews/search/public/nsIMsgSearchNotify.idl b/comm/mailnews/search/public/nsIMsgSearchNotify.idl
new file mode 100644
index 0000000000..1e3493ad83
--- /dev/null
+++ b/comm/mailnews/search/public/nsIMsgSearchNotify.idl
@@ -0,0 +1,30 @@
+/* -*- 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/. */
+
+#include "nsISupports.idl"
+
+interface nsIMsgDBHdr;
+interface nsIMsgSearchSession;
+interface nsIMsgFolder;
+
+// when a search is run, this interface is passed in as a listener
+// on the search.
+[scriptable, uuid(ca37784d-352b-4c39-8ccb-0abc1a93f681)]
+interface nsIMsgSearchNotify : nsISupports
+{
+ void onSearchHit(in nsIMsgDBHdr header, in nsIMsgFolder folder);
+
+ // notification that a search has finished.
+ void onSearchDone(in nsresult status);
+ /*
+ * until we can encode searches with a URI, this will be an
+ * out-of-bound way to connect a set of search terms to a datasource
+ */
+
+ /*
+ * called when a new search begins
+ */
+ void onNewSearch();
+};
diff --git a/comm/mailnews/search/public/nsIMsgSearchScopeTerm.idl b/comm/mailnews/search/public/nsIMsgSearchScopeTerm.idl
new file mode 100644
index 0000000000..63a130d9ea
--- /dev/null
+++ b/comm/mailnews/search/public/nsIMsgSearchScopeTerm.idl
@@ -0,0 +1,19 @@
+/* -*- Mode: IDL; 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/. */
+
+#include "nsIMsgSearchSession.idl"
+
+interface nsIMsgFolder;
+interface nsIMsgDBHdr;
+interface nsILineInputStream;
+interface nsIInputStream;
+
+[scriptable, uuid(934672c3-9b8f-488a-935d-87b4023fa0be)]
+interface nsIMsgSearchScopeTerm : nsISupports {
+ nsIInputStream getInputStream(in nsIMsgDBHdr aHdr);
+ void closeInputStream();
+ readonly attribute nsIMsgFolder folder;
+ readonly attribute nsIMsgSearchSession searchSession;
+};
diff --git a/comm/mailnews/search/public/nsIMsgSearchSession.idl b/comm/mailnews/search/public/nsIMsgSearchSession.idl
new file mode 100644
index 0000000000..024ce829b2
--- /dev/null
+++ b/comm/mailnews/search/public/nsIMsgSearchSession.idl
@@ -0,0 +1,130 @@
+/* -*- 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/. */
+
+#include "nsISupports.idl"
+#include "nsIMsgSearchValue.idl"
+
+interface nsIMsgSearchAdapter;
+interface nsIMsgSearchTerm;
+interface nsIMsgSearchNotify;
+interface nsIMsgDatabase;
+interface nsIMsgWindow;
+
+//////////////////////////////////////////////////////////////////////////////
+// The Msg Search Session is an interface designed to make constructing
+// searches easier. Clients typically build up search terms, and then run
+// the search
+//////////////////////////////////////////////////////////////////////////////
+
+[scriptable, uuid(1ed69bbf-7983-4602-9a9b-2f2263a78878)]
+interface nsIMsgSearchSession : nsISupports {
+
+/**
+ * add a search term to the search session
+ *
+ * @param attrib search attribute (e.g. nsMsgSearchAttrib::Subject)
+ * @param op search operator (e.g. nsMsgSearchOp::Contains)
+ * @param value search value (e.g. "Dogbert", see nsIMsgSearchValue)
+ * @param BooleanAND set to true if associated boolean operator is AND
+ * @param customString if attrib > nsMsgSearchAttrib::OtherHeader,
+ * a user defined arbitrary header
+ * if attrib == nsMsgSearchAttrib::Custom, the custom id
+ * otherwise ignored
+ */
+ void addSearchTerm(in nsMsgSearchAttribValue attrib,
+ in nsMsgSearchOpValue op,
+ in nsIMsgSearchValue value,
+ in boolean BooleanAND,
+ in string customString);
+
+ attribute Array<nsIMsgSearchTerm> searchTerms;
+
+ nsIMsgSearchTerm createTerm();
+ void appendTerm(in nsIMsgSearchTerm term);
+
+ /**
+ * @name Search notification flags
+ * These flags determine which notifications will be sent.
+ * @{
+ */
+ /// search started notification
+ const long onNewSearch = 0x1;
+
+ /// search finished notification
+ const long onSearchDone = 0x2;
+
+ /// search hit notification
+ const long onSearchHit = 0x4;
+
+ const long allNotifications = 0x7;
+ /** @} */
+
+ /**
+ * Add a listener to get notified of search starts, stops, and hits.
+ *
+ * @param aListener listener
+ * @param aNotifyFlags which notifications to send. Defaults to all
+ */
+ void registerListener(in nsIMsgSearchNotify aListener,
+ [optional] in long aNotifyFlags);
+ void unregisterListener(in nsIMsgSearchNotify listener);
+
+ readonly attribute unsigned long numSearchTerms;
+
+ readonly attribute nsIMsgSearchAdapter runningAdapter;
+
+ void getNthSearchTerm(in long whichTerm,
+ in nsMsgSearchAttribValue attrib,
+ in nsMsgSearchOpValue op,
+ in nsIMsgSearchValue value); // wrong, should be out
+
+ long countSearchScopes();
+
+ void getNthSearchScope(in long which,out nsMsgSearchScopeValue scopeId, out nsIMsgFolder folder);
+
+ /* add a scope (e.g. a mail folder) to the search */
+ void addScopeTerm(in nsMsgSearchScopeValue scope,
+ in nsIMsgFolder folder);
+
+ void addDirectoryScopeTerm(in nsMsgSearchScopeValue scope);
+
+ void clearScopes();
+
+ /* Call this function every time the scope changes! It informs the FE if
+ the current scope support custom header use. FEs should not display the
+ custom header dialog if custom headers are not supported */
+ [noscript] boolean ScopeUsesCustomHeaders(in nsMsgSearchScopeValue scope,
+ /* could be a folder or server based on scope */
+ in voidPtr selection,
+ in boolean forFilters);
+
+ /* use this to determine if your attribute is a string attrib */
+ boolean IsStringAttribute(in nsMsgSearchAttribValue attrib);
+
+ /* add all scopes of a given type to the search */
+ void AddAllScopes(in nsMsgSearchScopeValue attrib);
+
+ void search(in nsIMsgWindow aWindow);
+ void interruptSearch();
+
+ // these two methods are used when the search session is using
+ // a timer to do local search, and the search adapter needs
+ // to run a url (e.g., to reparse a local folder) and wants to
+ // pause the timer while running the url. This will fail if the
+ // current adapter is not using a timer.
+ void pauseSearch();
+ void resumeSearch();
+
+ boolean MatchHdr(in nsIMsgDBHdr aMsgHdr, in nsIMsgDatabase aDatabase);
+
+ void addSearchHit(in nsIMsgDBHdr header, in nsIMsgFolder folder);
+
+ readonly attribute long numResults;
+ attribute nsIMsgWindow window;
+
+ /* these longs are all actually of type nsMsgSearchBooleanOp */
+ const long BooleanOR=0;
+ const long BooleanAND=1;
+};
diff --git a/comm/mailnews/search/public/nsIMsgSearchTerm.idl b/comm/mailnews/search/public/nsIMsgSearchTerm.idl
new file mode 100644
index 0000000000..5724ba1c1a
--- /dev/null
+++ b/comm/mailnews/search/public/nsIMsgSearchTerm.idl
@@ -0,0 +1,153 @@
+/* -*- Mode: IDL; 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/. */
+
+#include "nsISupports.idl"
+#include "nsMsgSearchCore.idl"
+#include "nsIMsgSearchValue.idl"
+
+interface nsIMsgDBHdr;
+interface nsIMsgDatabase;
+interface nsIMsgSearchScopeTerm;
+
+[scriptable, uuid(705a2b5a-5efc-495c-897a-bef1161cd3c0)]
+interface nsIMsgSearchTerm : nsISupports {
+ attribute nsMsgSearchAttribValue attrib;
+ attribute nsMsgSearchOpValue op;
+ attribute nsIMsgSearchValue value;
+
+ attribute boolean booleanAnd;
+ attribute ACString arbitraryHeader;
+ /**
+ * Not to be confused with arbitraryHeader, which is a header in the
+ * rfc822 message. This is a property of the nsIMsgDBHdr, and may have
+ * nothing to do the message headers, e.g., gloda-id.
+ * value.str will be compared with nsIMsgHdr::GetProperty(hdrProperty).
+ */
+ attribute ACString hdrProperty;
+
+ /// identifier for a custom id used for this term, if any.
+ attribute ACString customId;
+
+ attribute boolean beginsGrouping;
+ attribute boolean endsGrouping;
+
+ /**
+ * Match the value against one of the emails found in the incoming
+ * 2047-encoded string.
+ */
+ boolean matchRfc822String(in ACString aString, in string charset);
+ /**
+ * Match the current header value against the incoming 2047-encoded string.
+ *
+ * This method will first apply the nsIMimeConverter decoding to the string
+ * (using the supplied parameters) and will then match the value against the
+ * decoded result.
+ */
+ boolean matchRfc2047String(in ACString aString, in string charset, in boolean charsetOverride);
+ boolean matchDate(in PRTime aTime);
+ boolean matchStatus(in unsigned long aStatus);
+ boolean matchPriority(in nsMsgPriorityValue priority);
+ boolean matchAge(in PRTime days);
+ boolean matchSize(in unsigned long size);
+ boolean matchJunkStatus(in string aJunkScore);
+ /*
+ * Test search term match for junkpercent
+ *
+ * @param aJunkPercent junkpercent for message (0-100, 100 is junk)
+ * @return true if matches
+ */
+ boolean matchJunkPercent(in unsigned long aJunkPercent);
+ /*
+ * Test search term match for junkscoreorigin
+ * @param aJunkScoreOrigin Who set junk score? Possible values:
+ * plugin filter imapflag user whitelist
+ * @return true if matches
+ */
+ boolean matchJunkScoreOrigin(in string aJunkScoreOrigin);
+
+ /**
+ * Test if the body of the passed in message matches "this" search term.
+ * @param aScopeTerm scope of search
+ * @param aOffset offset of message in message store.
+ * @param aLength length of message.
+ * @param aCharset folder charset.
+ * @param aMsg db msg hdr of message to match.
+ * @param aDB db containing msg header.
+ */
+ boolean matchBody(in nsIMsgSearchScopeTerm aScopeTerm,
+ in unsigned long long aOffset,
+ in unsigned long aLength,
+ in string aCharset,
+ in nsIMsgDBHdr aMsg,
+ in nsIMsgDatabase aDb);
+
+ /**
+ * Test if the arbitrary header specified by this search term
+ * matches the corresponding header in the passed in message.
+ *
+ * @param aScopeTerm scope of search
+ * @param aLength length of message
+ * @param aCharset The charset to apply to un-labeled non-UTF-8 data.
+ * @param aCharsetOverride If true, aCharset is used instead of any
+ * charset labeling other than UTF-8.
+ * @param aMsg The nsIMsgDBHdr of the message
+ * @param aDb The message database containing aMsg
+ * @param aHeaders A null-separated list of message headers.
+ * @param aForFilters Whether this is a filter or a search operation.
+ */
+ boolean matchArbitraryHeader(in nsIMsgSearchScopeTerm aScopeTerm,
+ in unsigned long aLength,
+ in string aCharset,
+ in boolean aCharsetOverride,
+ in nsIMsgDBHdr aMsg,
+ in nsIMsgDatabase aDb,
+ in ACString aHeaders,
+ in boolean aForFilters);
+
+ /**
+ * Compares value.str with nsIMsgHdr::GetProperty(hdrProperty).
+ * @param msg msg to match db hdr property of.
+ *
+ * @returns true if msg matches property, false otherwise.
+ */
+ boolean matchHdrProperty(in nsIMsgDBHdr msg);
+
+ /**
+ * Compares value.status with nsIMsgHdr::GetUint32Property(hdrProperty).
+ * @param msg msg to match db hdr property of.
+ *
+ * @returns true if msg matches property, false otherwise.
+ */
+ boolean matchUint32HdrProperty(in nsIMsgDBHdr msg);
+
+ /**
+ * Compares value.status with the folder flags of the msg's folder.
+ * @param msg msgHdr whose folder's flag we want to compare.
+ *
+ * @returns true if folder's flags match value.status, false otherwise.
+ */
+ boolean matchFolderFlag(in nsIMsgDBHdr msg);
+
+ readonly attribute boolean matchAllBeforeDeciding;
+
+ readonly attribute ACString termAsString;
+ boolean matchKeyword(in ACString keyword); // used for tag searches
+ attribute boolean matchAll;
+ /**
+ * Does the message match the custom search term?
+ *
+ * @param msg message database object representing the message
+ *
+ * @return true if message matches
+ */
+ boolean matchCustom(in nsIMsgDBHdr msg);
+
+ /**
+ * Returns a nsMsgSearchAttribValue value corresponding to a field string from
+ * the nsMsgSearchTerm.cpp::SearchAttribEntryTable table.
+ * Does not handle custom attributes yet.
+ */
+ nsMsgSearchAttribValue getAttributeFromString(in string aAttribName);
+};
diff --git a/comm/mailnews/search/public/nsIMsgSearchValidityManager.idl b/comm/mailnews/search/public/nsIMsgSearchValidityManager.idl
new file mode 100644
index 0000000000..dbc7958bd8
--- /dev/null
+++ b/comm/mailnews/search/public/nsIMsgSearchValidityManager.idl
@@ -0,0 +1,26 @@
+/* -*- Mode: IDL; 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/. */
+
+#include "nsISupports.idl"
+#include "nsIMsgSearchValidityTable.idl"
+
+typedef long nsMsgSearchValidityScope;
+
+[scriptable, uuid(6A352055-DE6E-49d2-A256-89E0B9EC405E)]
+interface nsIMsgSearchValidityManager : nsISupports {
+ nsIMsgSearchValidityTable getTable(in nsMsgSearchValidityScope scope);
+
+ /**
+ * Given a search attribute (which is an internal numerical id), return
+ * the string name that you can use as a key to look up the localized
+ * string in the search-attributes.properties file.
+ *
+ * @param aSearchAttribute attribute type from interface nsMsgSearchAttrib
+ *
+ * @return localization-friendly string representation
+ * of the attribute
+ */
+ AString getAttributeProperty(in nsMsgSearchAttribValue aSearchAttribute);
+};
diff --git a/comm/mailnews/search/public/nsIMsgSearchValidityTable.idl b/comm/mailnews/search/public/nsIMsgSearchValidityTable.idl
new file mode 100644
index 0000000000..75fd4efd51
--- /dev/null
+++ b/comm/mailnews/search/public/nsIMsgSearchValidityTable.idl
@@ -0,0 +1,32 @@
+/* -*- Mode: IDL; 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/. */
+
+#include "nsISupports.idl"
+#include "nsMsgSearchCore.idl"
+
+[scriptable, uuid(b07f1cb6-fae9-4d92-9edb-03f9ad249c66)]
+interface nsIMsgSearchValidityTable : nsISupports {
+
+ void setAvailable(in nsMsgSearchAttribValue attrib,
+ in nsMsgSearchOpValue op, in boolean active);
+ void setEnabled(in nsMsgSearchAttribValue attrib,
+ in nsMsgSearchOpValue op, in boolean enabled);
+ void setValidButNotShown(in nsMsgSearchAttribValue attrib,
+ in nsMsgSearchOpValue op, in boolean valid);
+
+ boolean getAvailable(in nsMsgSearchAttribValue attrib,
+ in nsMsgSearchOpValue op);
+ boolean getEnabled(in nsMsgSearchAttribValue attrib,
+ in nsMsgSearchOpValue op);
+ boolean getValidButNotShown(in nsMsgSearchAttribValue attrib,
+ in nsMsgSearchOpValue op);
+
+ readonly attribute long numAvailAttribs;
+
+ Array<nsMsgSearchAttribValue> getAvailableAttributes();
+ Array<nsMsgSearchOpValue> getAvailableOperators(in nsMsgSearchAttribValue attrib);
+
+ void setDefaultAttrib(in nsMsgSearchAttribValue defaultAttrib);
+};
diff --git a/comm/mailnews/search/public/nsIMsgSearchValue.idl b/comm/mailnews/search/public/nsIMsgSearchValue.idl
new file mode 100644
index 0000000000..6a4cec0b28
--- /dev/null
+++ b/comm/mailnews/search/public/nsIMsgSearchValue.idl
@@ -0,0 +1,35 @@
+/* -*- 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/. */
+
+#include "nsMsgSearchCore.idl"
+
+interface nsIMsgFolder;
+
+[scriptable, uuid(783758a0-cdb5-11dc-95ff-0800200c9a66)]
+interface nsIMsgSearchValue : nsISupports {
+ // type of object
+ attribute nsMsgSearchAttribValue attrib;
+
+ // accessing these will throw an exception if the above
+ // attribute does not match the type!
+ attribute AString str;
+ attribute nsMsgPriorityValue priority;
+ attribute PRTime date;
+ // see nsMsgMessageFlags.idl and nsMsgFolderFlags.idl
+ attribute unsigned long status;
+ attribute unsigned long size;
+ attribute nsMsgKey msgKey;
+ attribute long age; // in days
+ attribute nsIMsgFolder folder;
+ attribute nsMsgJunkStatus junkStatus;
+ /*
+ * junkPercent is set by the message filter plugin, and is approximately
+ * proportional to the probability that a message is junk.
+ * (range 0-100, 100 is junk)
+ */
+ attribute unsigned long junkPercent;
+
+ AString toString();
+};
diff --git a/comm/mailnews/search/public/nsIMsgTraitService.idl b/comm/mailnews/search/public/nsIMsgTraitService.idl
new file mode 100644
index 0000000000..23ade21d8d
--- /dev/null
+++ b/comm/mailnews/search/public/nsIMsgTraitService.idl
@@ -0,0 +1,182 @@
+/* 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 interface provides management of traits that are used to categorize
+ * messages. A trait is some characteristic of a message, such as being "junk"
+ * or "personal", that may be discoverable by analysis of the message.
+ *
+ * Traits are described by a universal identifier "id" as a string, as well
+ * as a local integer identifier "index". One purpose of this service is to
+ * provide the mapping between those forms.
+ *
+ * Recommended (but not required) format for id:
+ * "extensionName@example.org#traitName"
+ */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(2CB15FB0-A912-40d3-8882-F2765C75655F)]
+interface nsIMsgTraitService : nsISupports
+{
+ /**
+ * the highest ever index for a registered trait. The first trait is 1,
+ * == 0 means no traits are defined
+ */
+ readonly attribute long lastIndex;
+
+ /**
+ * Register a trait. May be called multiple times, but subsequent
+ * calls do not register the trait
+ *
+ * @param id the trait universal identifier
+ *
+ * @return the internal index for the registered trait if newly
+ * registered, else 0
+ */
+ unsigned long registerTrait(in ACString id);
+
+ /**
+ * Unregister a trait.
+ *
+ * @param id the trait universal identifier
+ */
+ void unRegisterTrait(in ACString id);
+
+ /**
+ * is a trait registered?
+ *
+ * @param id the trait universal identifier
+ *
+ * @return true if registered
+ */
+ boolean isRegistered(in ACString id);
+
+ /**
+ * set the trait name, which is an optional short description of the trait
+ *
+ * @param id the trait universal identifier
+ * @param name description of the trait.
+ */
+ void setName(in ACString id, in ACString name);
+
+ /**
+ * get the trait name, which is an optional short description of the trait
+ *
+ * @param id the trait universal identifier
+ *
+ * @return description of the trait
+ */
+ ACString getName(in ACString id);
+
+ /**
+ * get the internal index number for the trait.
+ *
+ * @param id the trait universal identifier
+ *
+ * @return internal index number for the trait
+ */
+ unsigned long getIndex(in ACString id);
+
+ /**
+ * get the trait universal identifier for an internal trait index
+ *
+ * @param index the internal identifier for the trait
+ *
+ * @return trait universal identifier
+ */
+ ACString getId(in unsigned long index);
+
+ /**
+ * enable the trait for analysis. Each enabled trait will be analyzed by
+ * the bayesian code. The enabled trait is the "pro" trait that represents
+ * messages matching the trait. Each enabled trait also needs a corresponding
+ * anti trait defined, which represents messages that do not match the trait.
+ * The anti trait does not need to be enabled
+ *
+ * @param id the trait universal identifier
+ * @param enabled should this trait be processed by the bayesian analyzer?
+ */
+ void setEnabled(in ACString id, in boolean enabled);
+
+ /**
+ * Should this trait be processed by the bayes analyzer?
+ *
+ * @param id the trait universal identifier
+ *
+ * @return true if this is a "pro" trait to process
+ */
+ boolean getEnabled(in ACString id);
+
+ /**
+ * set the anti trait, which indicates messages that have been marked as
+ * NOT matching a particular trait.
+ *
+ * @param id the trait universal identifier
+ * @param antiId trait id for messages marked as not matching the trait
+ */
+ void setAntiId(in ACString id, in ACString antiId);
+
+ /**
+ * get the id of traits that do not match a particular trait
+ *
+ * @param id the trait universal identifier for a "pro" trait
+ *
+ * @return universal trait identifier for an "anti" trait that does not
+ * match the "pro" trait messages
+ */
+ ACString getAntiId(in ACString id);
+
+ /**
+ * Get an array of "pro" traits to be analyzed by the bayesian code. This is
+ * a "pro" trait of messages that match the trait.
+ * Only enabled traits are returned.
+ * This should return the same number of indices as the corresponding call to
+ * getEnabledAntiIndices().
+ *
+ * @return an array of trait internal indices for "pro" trait to analyze
+ */
+ Array<unsigned long> getEnabledProIndices();
+
+ /**
+ * Get an array of "anti" traits to be analyzed by the bayesian code. This is
+ * a "anti" trait of messages that do not match the trait.
+ * Only enabled traits are returned.
+ * This should return the same number of indices as the corresponding call to
+ * getEnabledProIndices().
+ *
+ * @return an array of trait internal indices for "anti" trait to analyze
+ */
+ Array<unsigned long> getEnabledAntiIndices();
+
+ /**
+ * Add a trait as an alias of another trait. An alias is a trait whose
+ * counts will be combined with the aliased trait. This allows multiple sets
+ * of corpus data to be used to provide information on a single message
+ * characteristic, while allowing each individual set of corpus data to
+ * retain its own identity.
+ *
+ * @param aTraitIndex the internal identifier for the aliased trait
+ * @param aTraitAlias the internal identifier for the alias to add
+ */
+ void addAlias(in unsigned long aTraitIndex, in unsigned long aTraitAlias);
+
+ /**
+ * Removes a trait as an alias of another trait.
+ *
+ * @param aTraitIndex the internal identifier for the aliased trait
+ * @param aTraitAlias the internal identifier for the alias to remove
+ */
+ void removeAlias(in unsigned long aTraitIndex, in unsigned long aTraitAlias);
+
+ /**
+ * Get an array of trait aliases for a trait index, if any
+ *
+ * @param aTraitIndex the internal identifier for the aliased trait
+ *
+ * @return an array of internal identifiers for aliases
+ */
+ Array<unsigned long> getAliases(in unsigned long aTraitIndex);
+
+};
diff --git a/comm/mailnews/search/public/nsMsgBodyHandler.h b/comm/mailnews/search/public/nsMsgBodyHandler.h
new file mode 100644
index 0000000000..1252e2c20b
--- /dev/null
+++ b/comm/mailnews/search/public/nsMsgBodyHandler.h
@@ -0,0 +1,110 @@
+/* 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 __nsMsgBodyHandler_h
+#define __nsMsgBodyHandler_h
+
+#include "nsIMsgSearchScopeTerm.h"
+#include "nsILineInputStream.h"
+#include "nsIMsgDatabase.h"
+
+//---------------------------------------------------------------------------
+// nsMsgBodyHandler: used to retrieve lines from POP and IMAP offline messages.
+// This is a helper class used by nsMsgSearchTerm::MatchBody
+//---------------------------------------------------------------------------
+class nsMsgBodyHandler {
+ public:
+ nsMsgBodyHandler(nsIMsgSearchScopeTerm*, uint32_t length, nsIMsgDBHdr* msg,
+ nsIMsgDatabase* db);
+
+ // we can also create a body handler when doing arbitrary header
+ // filtering...we need the list of headers and the header size as well
+ // if we are doing filtering...if ForFilters is false, headers and
+ // headersSize is ignored!!!
+ nsMsgBodyHandler(nsIMsgSearchScopeTerm*, uint32_t length, nsIMsgDBHdr* msg,
+ nsIMsgDatabase* db,
+ const char* headers /* NULL terminated list of headers */,
+ uint32_t headersSize, bool ForFilters);
+
+ virtual ~nsMsgBodyHandler();
+
+ // Returns next message line in buf and the applicable charset, if found.
+ // The return value is the length of 'buf' or -1 for EOF.
+ int32_t GetNextLine(nsCString& buf, nsCString& charset);
+ bool IsQP() { return m_partIsQP; }
+
+ // Transformations
+ void SetStripHeaders(bool strip) { m_stripHeaders = strip; }
+
+ protected:
+ void Initialize(); // common initialization code
+
+ // filter related methods. For filtering we always use the headers
+ // list instead of the database...
+ bool m_Filtering;
+ int32_t GetNextFilterLine(nsCString& buf);
+ // pointer into the headers list in the original message hdr db...
+ const char* m_headers;
+ uint32_t m_headersSize;
+ uint32_t m_headerBytesRead;
+
+ // local / POP related methods
+ void OpenLocalFolder();
+
+ // goes through the mail folder
+ int32_t GetNextLocalLine(nsCString& buf);
+
+ nsIMsgSearchScopeTerm* m_scope;
+ nsCOMPtr<nsILineInputStream> m_fileLineStream;
+ nsCOMPtr<nsIFile> m_localFile;
+
+ /**
+ * The number of lines in the message. If |m_lineCountInBodyLines| then this
+ * is the number of body lines, otherwise this is the entire number of lines
+ * in the message. This is important so we know when to stop reading the file
+ * without accidentally reading part of the next message.
+ */
+ uint32_t m_numLocalLines;
+ /**
+ * When true, |m_numLocalLines| is the number of body lines in the message,
+ * when false it is the entire number of lines in the message.
+ *
+ * When a message is an offline IMAP or news message, then the number of lines
+ * will be the entire number of lines, so this should be false. When the
+ * message is a local message, the number of lines will be the number of body
+ * lines.
+ */
+ bool m_lineCountInBodyLines;
+
+ // Offline IMAP related methods & state
+
+ nsCOMPtr<nsIMsgDBHdr> m_msgHdr;
+ nsCOMPtr<nsIMsgDatabase> m_db;
+
+ // Transformations
+ // With the exception of m_isMultipart, these all apply to the various parts
+ bool m_stripHeaders; // true if we're supposed to strip of message headers
+ bool m_pastMsgHeaders; // true if we've already skipped over the message
+ // headers
+ bool m_pastPartHeaders; // true if we've already skipped over the part
+ // headers
+ bool m_partIsQP; // true if the Content-Transfer-Encoding header claims
+ // quoted-printable
+ bool m_partIsHtml; // true if the Content-type header claims text/html
+ bool m_base64part; // true if the current part is in base64
+ bool m_isMultipart; // true if the message is a multipart/* message
+ bool m_partIsText; // true if the current part is text/*
+ bool m_inMessageAttachment; // true if current part is message/*
+
+ nsTArray<nsCString> m_boundaries; // The boundary strings to look for
+ nsCString m_partCharset; // The charset found in the part
+
+ // See implementation for comments
+ int32_t ApplyTransformations(const nsCString& line, int32_t length,
+ bool& returnThisLine, nsCString& buf);
+ void SniffPossibleMIMEHeader(const nsCString& line);
+ static void StripHtml(nsCString& buf);
+ static void Base64Decode(nsCString& buf);
+};
+#endif
diff --git a/comm/mailnews/search/public/nsMsgFilterCore.idl b/comm/mailnews/search/public/nsMsgFilterCore.idl
new file mode 100644
index 0000000000..42adc5e730
--- /dev/null
+++ b/comm/mailnews/search/public/nsMsgFilterCore.idl
@@ -0,0 +1,62 @@
+/* -*- Mode: IDL; 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/. */
+
+#include "nsMsgSearchCore.idl"
+
+typedef long nsMsgFilterTypeType;
+
+[scriptable,uuid(b963a9c6-3a75-4d91-9f79-7186418d4d2d)]
+interface nsMsgFilterType : nsISupports {
+ /* these longs are all actually of type nsMsgFilterTypeType */
+ const long None = 0x00;
+ const long InboxRule = 0x01;
+ const long InboxJavaScript = 0x02;
+ const long Inbox = InboxRule | InboxJavaScript;
+ const long NewsRule = 0x04;
+ const long NewsJavaScript = 0x08;
+ const long News = NewsRule | NewsJavaScript;
+ const long Incoming = Inbox | News;
+ const long Manual = 0x10;
+ const long PostPlugin = 0x20; // After bayes filtering
+ const long PostOutgoing = 0x40; // After sending
+ const long Archive = 0x80; // Before archiving
+ const long Periodic = 0x100;// On a repeating timer
+ const long All = Incoming | Manual;
+};
+
+typedef long nsMsgFilterMotionValue;
+
+typedef long nsMsgFilterIndex;
+
+typedef long nsMsgRuleActionType;
+
+[scriptable, uuid(7726FE79-AFA3-4a39-8292-733AEE288737)]
+interface nsMsgFilterAction : nsISupports {
+
+ // Custom Action.
+ const long Custom=-1;
+ /* if you change these, you need to update filter.properties,
+ look for filterActionX */
+ /* these longs are all actually of type nsMsgFilterActionType */
+ const long None=0; /* uninitialized state */
+ const long MoveToFolder=1;
+ const long ChangePriority=2;
+ const long Delete=3;
+ const long MarkRead=4;
+ const long KillThread=5;
+ const long WatchThread=6;
+ const long MarkFlagged=7;
+ const long Reply=9;
+ const long Forward=10;
+ const long StopExecution=11;
+ const long DeleteFromPop3Server=12;
+ const long LeaveOnPop3Server=13;
+ const long JunkScore=14;
+ const long FetchBodyFromPop3Server=15;
+ const long CopyToFolder=16;
+ const long AddTag=17;
+ const long KillSubthread=18;
+ const long MarkUnread=19;
+};
diff --git a/comm/mailnews/search/public/nsMsgResultElement.h b/comm/mailnews/search/public/nsMsgResultElement.h
new file mode 100644
index 0000000000..104e6a3771
--- /dev/null
+++ b/comm/mailnews/search/public/nsMsgResultElement.h
@@ -0,0 +1,40 @@
+/* -*- 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 __nsMsgResultElement_h
+#define __nsMsgResultElement_h
+
+#include "nsMsgSearchCore.h"
+#include "nsIMsgSearchAdapter.h"
+#include "nsTArray.h"
+
+// nsMsgResultElement specifies a single search hit.
+
+//---------------------------------------------------------------------------
+// nsMsgResultElement is a list of attribute/value pairs which are used to
+// represent a search hit without requiring a message header or server
+// connection
+//---------------------------------------------------------------------------
+
+class nsMsgResultElement {
+ public:
+ explicit nsMsgResultElement(nsIMsgSearchAdapter*);
+ virtual ~nsMsgResultElement();
+
+ static nsresult AssignValues(nsIMsgSearchValue* src, nsMsgSearchValue* dst);
+ nsresult GetValue(nsMsgSearchAttribValue, nsMsgSearchValue**) const;
+ nsresult AddValue(nsIMsgSearchValue*);
+ nsresult AddValue(nsMsgSearchValue*);
+
+ nsresult GetPrettyName(nsMsgSearchValue**);
+ nsresult Open(void* window);
+
+ nsTArray<nsCOMPtr<nsIMsgSearchValue> > m_valueList;
+ nsIMsgSearchAdapter* m_adapter;
+
+ protected:
+};
+
+#endif
diff --git a/comm/mailnews/search/public/nsMsgSearchAdapter.h b/comm/mailnews/search/public/nsMsgSearchAdapter.h
new file mode 100644
index 0000000000..fbfa5176e6
--- /dev/null
+++ b/comm/mailnews/search/public/nsMsgSearchAdapter.h
@@ -0,0 +1,242 @@
+/* -*- 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 _nsMsgSearchAdapter_H_
+#define _nsMsgSearchAdapter_H_
+
+#include "nsMsgSearchCore.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsIMsgSearchAdapter.h"
+#include "nsIMsgSearchValidityTable.h"
+#include "nsIMsgSearchValidityManager.h"
+#include "nsIMsgSearchTerm.h"
+#include "nsINntpIncomingServer.h"
+
+class nsIMsgSearchScopeTerm;
+
+//-----------------------------------------------------------------------------
+// These Adapter classes contain the smarts to convert search criteria from
+// the canonical structures in msg_srch.h into whatever format is required
+// by their protocol.
+//
+// There is a separate Adapter class for area (pop, imap, nntp, ldap) to contain
+// the special smarts for that protocol.
+//-----------------------------------------------------------------------------
+
+class nsMsgSearchAdapter : public nsIMsgSearchAdapter {
+ public:
+ nsMsgSearchAdapter(nsIMsgSearchScopeTerm*,
+ nsTArray<RefPtr<nsIMsgSearchTerm>> const&);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGSEARCHADAPTER
+
+ nsIMsgSearchScopeTerm* m_scope;
+ nsTArray<RefPtr<nsIMsgSearchTerm>>
+ m_searchTerms; /* linked list of criteria terms */
+
+ nsString m_defaultCharset = u"UTF-8"_ns;
+
+ static nsresult EncodeImap(
+ char** ppEncoding, nsTArray<RefPtr<nsIMsgSearchTerm>> const& searchTerms,
+ const char16_t* srcCharset, const char16_t* destCharset,
+ bool reallyDredd = false);
+
+ static nsresult EncodeImapValue(char* encoding, const char* value,
+ bool useQuotes, bool reallyDredd);
+
+ static char* GetImapCharsetParam(const char16_t* destCharset);
+ static char16_t* EscapeSearchUrl(const char16_t* nntpCommand);
+ static char16_t* EscapeImapSearchProtocol(const char16_t* imapCommand);
+ static char16_t* EscapeQuoteImapSearchProtocol(const char16_t* imapCommand);
+ static char* UnEscapeSearchUrl(const char* commandSpecificData);
+ // This stuff lives in the base class because the IMAP search syntax
+ // is used by the Dredd SEARCH command as well as IMAP itself
+ static const char* m_kImapBefore;
+ static const char* m_kImapBody;
+ static const char* m_kImapCC;
+ static const char* m_kImapFrom;
+ static const char* m_kImapNot;
+ static const char* m_kImapOr;
+ static const char* m_kImapSince;
+ static const char* m_kImapSubject;
+ static const char* m_kImapTo;
+ static const char* m_kImapHeader;
+ static const char* m_kImapAnyText;
+ static const char* m_kImapKeyword;
+ static const char* m_kNntpKeywords;
+ static const char* m_kImapSentOn;
+ static const char* m_kImapSeen;
+ static const char* m_kImapAnswered;
+ static const char* m_kImapNotSeen;
+ static const char* m_kImapNotAnswered;
+ static const char* m_kImapCharset;
+ static const char* m_kImapUnDeleted;
+ static const char* m_kImapSizeSmaller;
+ static const char* m_kImapSizeLarger;
+ static const char* m_kImapNew;
+ static const char* m_kImapNotNew;
+ static const char* m_kImapFlagged;
+ static const char* m_kImapNotFlagged;
+
+ protected:
+ virtual ~nsMsgSearchAdapter();
+ typedef enum _msg_TransformType {
+ kOverwrite, /* "John Doe" -> "John*Doe", simple contains */
+ kInsert, /* "John Doe" -> "John* Doe", name completion */
+ kSurround /* "John Doe" -> "John* *Doe", advanced contains */
+ } msg_TransformType;
+
+ char* TransformSpacesToStars(const char*, msg_TransformType transformType);
+ nsresult OpenNewsResultInUnknownGroup(nsMsgResultElement*);
+
+ static nsresult EncodeImapTerm(nsIMsgSearchTerm*, bool reallyDredd,
+ const char16_t* srcCharset,
+ const char16_t* destCharset, char** ppOutTerm);
+};
+
+//-----------------------------------------------------------------------------
+// Validity checking for attrib/op pairs. We need to know what operations are
+// legal in three places:
+// 1. when the FE brings up the dialog box and needs to know how to build
+// the menus and enable their items
+// 2. when the FE fires off a search, we need to check their lists for
+// correctness
+// 3. for on-the-fly capability negotiation e.g. with XSEARCH-capable news
+// servers
+//-----------------------------------------------------------------------------
+
+class nsMsgSearchValidityTable final : public nsIMsgSearchValidityTable {
+ public:
+ nsMsgSearchValidityTable();
+ NS_DECL_NSIMSGSEARCHVALIDITYTABLE
+ NS_DECL_ISUPPORTS
+
+ protected:
+ int m_numAvailAttribs; // number of rows with at least one available operator
+ typedef struct vtBits {
+ uint16_t bitEnabled : 1;
+ uint16_t bitAvailable : 1;
+ uint16_t bitValidButNotShown : 1;
+ } vtBits;
+ vtBits m_table[nsMsgSearchAttrib::kNumMsgSearchAttributes]
+ [nsMsgSearchOp::kNumMsgSearchOperators];
+
+ private:
+ ~nsMsgSearchValidityTable() {}
+ nsMsgSearchAttribValue m_defaultAttrib;
+};
+
+// Using getters and setters seems a little nicer then dumping the 2-D array
+// syntax all over the code
+#define CHECK_AO \
+ if (a < 0 || a >= nsMsgSearchAttrib::kNumMsgSearchAttributes || o < 0 || \
+ o >= nsMsgSearchOp::kNumMsgSearchOperators) \
+ return NS_ERROR_ILLEGAL_VALUE;
+inline nsresult nsMsgSearchValidityTable::SetAvailable(int a, int o, bool b) {
+ CHECK_AO;
+ m_table[a][o].bitAvailable = b;
+ return NS_OK;
+}
+inline nsresult nsMsgSearchValidityTable::SetEnabled(int a, int o, bool b) {
+ CHECK_AO;
+ m_table[a][o].bitEnabled = b;
+ return NS_OK;
+}
+inline nsresult nsMsgSearchValidityTable::SetValidButNotShown(int a, int o,
+ bool b) {
+ CHECK_AO;
+ m_table[a][o].bitValidButNotShown = b;
+ return NS_OK;
+}
+
+inline nsresult nsMsgSearchValidityTable::GetAvailable(int a, int o,
+ bool* aResult) {
+ CHECK_AO;
+ *aResult = m_table[a][o].bitAvailable;
+ return NS_OK;
+}
+inline nsresult nsMsgSearchValidityTable::GetEnabled(int a, int o,
+ bool* aResult) {
+ CHECK_AO;
+ *aResult = m_table[a][o].bitEnabled;
+ return NS_OK;
+}
+inline nsresult nsMsgSearchValidityTable::GetValidButNotShown(int a, int o,
+ bool* aResult) {
+ CHECK_AO;
+ *aResult = m_table[a][o].bitValidButNotShown;
+ return NS_OK;
+}
+#undef CHECK_AO
+
+class nsMsgSearchValidityManager : public nsIMsgSearchValidityManager {
+ public:
+ nsMsgSearchValidityManager();
+
+ protected:
+ virtual ~nsMsgSearchValidityManager();
+
+ public:
+ NS_DECL_NSIMSGSEARCHVALIDITYMANAGER
+ NS_DECL_ISUPPORTS
+
+ nsresult GetTable(int, nsMsgSearchValidityTable**);
+
+ protected:
+ // There's one global validity manager that everyone uses. You *could* do
+ // this with static members of the adapter classes, but having a dedicated
+ // object makes cleanup of these tables (at shutdown-time) automagic.
+
+ nsCOMPtr<nsIMsgSearchValidityTable> m_offlineMailTable;
+ nsCOMPtr<nsIMsgSearchValidityTable> m_offlineMailFilterTable;
+ nsCOMPtr<nsIMsgSearchValidityTable> m_onlineMailTable;
+ nsCOMPtr<nsIMsgSearchValidityTable> m_onlineMailFilterTable;
+ nsCOMPtr<nsIMsgSearchValidityTable> m_onlineManualFilterTable;
+
+ nsCOMPtr<nsIMsgSearchValidityTable> m_newsTable; // online news
+
+ // Local news tables, used for local news searching or offline.
+ nsCOMPtr<nsIMsgSearchValidityTable> m_localNewsTable; // base table
+ nsCOMPtr<nsIMsgSearchValidityTable> m_localNewsJunkTable; // base + junk
+ nsCOMPtr<nsIMsgSearchValidityTable> m_localNewsBodyTable; // base + body
+ nsCOMPtr<nsIMsgSearchValidityTable>
+ m_localNewsJunkBodyTable; // base + junk + body
+ nsCOMPtr<nsIMsgSearchValidityTable> m_ldapTable;
+ nsCOMPtr<nsIMsgSearchValidityTable> m_ldapAndTable;
+ nsCOMPtr<nsIMsgSearchValidityTable> m_localABTable;
+ nsCOMPtr<nsIMsgSearchValidityTable> m_localABAndTable;
+ nsCOMPtr<nsIMsgSearchValidityTable> m_newsFilterTable;
+
+ nsresult NewTable(nsIMsgSearchValidityTable**);
+
+ nsresult InitOfflineMailTable();
+ nsresult InitOfflineMailFilterTable();
+ nsresult InitOnlineMailTable();
+ nsresult InitOnlineMailFilterTable();
+ nsresult InitOnlineManualFilterTable();
+ nsresult InitNewsTable();
+ nsresult InitLocalNewsTable();
+ nsresult InitLocalNewsJunkTable();
+ nsresult InitLocalNewsBodyTable();
+ nsresult InitLocalNewsJunkBodyTable();
+ nsresult InitNewsFilterTable();
+
+ // set the custom headers in the table, changes whenever
+ // "mailnews.customHeaders" pref changes.
+ nsresult SetOtherHeadersInTable(nsIMsgSearchValidityTable* table,
+ const char* customHeaders);
+
+ nsresult InitLdapTable();
+ nsresult InitLdapAndTable();
+ nsresult InitLocalABTable();
+ nsresult InitLocalABAndTable();
+ nsresult SetUpABTable(nsIMsgSearchValidityTable* aTable, bool isOrTable);
+ nsresult EnableDirectoryAttribute(nsIMsgSearchValidityTable* table,
+ nsMsgSearchAttribValue aSearchAttrib);
+};
+
+#endif
diff --git a/comm/mailnews/search/public/nsMsgSearchBoolExpression.h b/comm/mailnews/search/public/nsMsgSearchBoolExpression.h
new file mode 100644
index 0000000000..1eefd81fcc
--- /dev/null
+++ b/comm/mailnews/search/public/nsMsgSearchBoolExpression.h
@@ -0,0 +1,110 @@
+/* -*- 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/. */
+
+#include "nsMsgSearchCore.h"
+
+#ifndef __nsMsgSearchBoolExpression_h
+# define __nsMsgSearchBoolExpression_h
+
+//-----------------------------------------------------------------------------
+// nsMsgSearchBoolExpression is a class added to provide AND/OR terms in search
+// queries.
+// A nsMsgSearchBoolExpression contains either a search term or two
+// nsMsgSearchBoolExpressions and
+// a boolean operator.
+// I (mscott) am placing it here for now....
+//-----------------------------------------------------------------------------
+
+/* CBoolExpression --> encapsulates one or more search terms by internally
+ representing the search terms and their boolean operators as a binary
+ expression tree. Each node in the tree consists of either
+ (1) a boolean operator and two nsMsgSearchBoolExpressions or
+ (2) if the node is a leaf node then it contains a search term.
+ With each search term that is part of the expression we may also keep
+ a character string. The character
+ string is used to store the IMAP/NNTP encoding of the search term. This
+ makes generating a search encoding (for online) easier.
+
+ For IMAP/NNTP: nsMsgSearchBoolExpression has/assumes knowledge about how
+ AND and OR search terms are combined according to IMAP4 and NNTP protocol.
+ That is the only piece of IMAP/NNTP knowledge it is aware of.
+
+ Order of Evaluation: Okay, the way in which the boolean expression tree
+ is put together directly effects the order of evaluation. We currently
+ support left to right evaluation.
+ Supporting other order of evaluations involves adding new internal add
+ term methods.
+ */
+
+class nsMsgSearchBoolExpression {
+ public:
+ // create a leaf node expression
+ explicit nsMsgSearchBoolExpression(nsIMsgSearchTerm* aNewTerm,
+ char* aEncodingString = NULL);
+
+ // create a non-leaf node expression containing 2 expressions
+ // and a boolean operator
+ nsMsgSearchBoolExpression(nsMsgSearchBoolExpression*,
+ nsMsgSearchBoolExpression*,
+ nsMsgSearchBooleanOperator boolOp);
+
+ nsMsgSearchBoolExpression();
+ ~nsMsgSearchBoolExpression(); // recursively destroys all sub
+ // expressions as well
+
+ // accessors
+
+ // Offline
+ static nsMsgSearchBoolExpression* AddSearchTerm(
+ nsMsgSearchBoolExpression* aOrigExpr, nsIMsgSearchTerm* aNewTerm,
+ char* aEncodingStr); // IMAP/NNTP
+ static nsMsgSearchBoolExpression* AddExpressionTree(
+ nsMsgSearchBoolExpression* aOrigExpr,
+ nsMsgSearchBoolExpression* aExpression, bool aBoolOp);
+
+ // parses the expression tree and all
+ // expressions underneath this node to
+ // determine if the end result is true or false.
+ bool OfflineEvaluate(nsIMsgDBHdr* msgToMatch, const char* defaultCharset,
+ nsIMsgSearchScopeTerm* scope, nsIMsgDatabase* db,
+ const nsACString& headers, bool Filtering);
+
+ // assuming the expression is for online
+ // searches, determine the length of the
+ // resulting IMAP/NNTP encoding string
+ int32_t CalcEncodeStrSize();
+
+ // fills pre-allocated
+ // memory in buffer with
+ // the IMAP/NNTP encoding for the expression
+ void GenerateEncodeStr(nsCString* buffer);
+
+ // if we are not a leaf node, then we have two other expressions
+ // and a boolean operator
+ nsMsgSearchBoolExpression* m_leftChild;
+ nsMsgSearchBoolExpression* m_rightChild;
+ nsMsgSearchBooleanOperator m_boolOp;
+
+ protected:
+ // if we are a leaf node, all we have is a search term
+
+ nsIMsgSearchTerm* m_term;
+
+ // store IMAP/NNTP encoding for the search term if applicable
+ nsCString m_encodingStr;
+
+ // internal methods
+
+ // the idea is to separate the public interface for adding terms to
+ // the expression tree from the order of evaluation which influences
+ // how we internally construct the tree. Right now, we are supporting
+ // left to right evaluation so the tree is constructed to represent
+ // that by calling leftToRightAddTerm. If future forms of evaluation
+ // need to be supported, add new methods here for proper tree construction.
+ nsMsgSearchBoolExpression* leftToRightAddTerm(nsIMsgSearchTerm* newTerm,
+ char* encodingStr);
+};
+
+#endif
diff --git a/comm/mailnews/search/public/nsMsgSearchCore.idl b/comm/mailnews/search/public/nsMsgSearchCore.idl
new file mode 100644
index 0000000000..77b18dfa57
--- /dev/null
+++ b/comm/mailnews/search/public/nsMsgSearchCore.idl
@@ -0,0 +1,206 @@
+/* -*- 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/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+interface nsIMsgFolder;
+
+interface nsIMsgDatabase;
+interface nsIMsgDBHdr;
+
+[scriptable, uuid(6e893e59-af98-4f62-a326-0f00f32147cd)]
+
+interface nsMsgSearchScope : nsISupports {
+ const nsMsgSearchScopeValue offlineMail = 0;
+ const nsMsgSearchScopeValue offlineMailFilter = 1;
+ const nsMsgSearchScopeValue onlineMail = 2;
+ const nsMsgSearchScopeValue onlineMailFilter = 3;
+ /// offline news, base table, no body or junk
+ const nsMsgSearchScopeValue localNews = 4;
+ const nsMsgSearchScopeValue news = 5;
+ const nsMsgSearchScopeValue newsEx = 6;
+ const nsMsgSearchScopeValue LDAP = 7;
+ const nsMsgSearchScopeValue LocalAB = 8;
+ const nsMsgSearchScopeValue allSearchableGroups = 9;
+ const nsMsgSearchScopeValue newsFilter = 10;
+ const nsMsgSearchScopeValue LocalABAnd = 11;
+ const nsMsgSearchScopeValue LDAPAnd = 12;
+ // IMAP and NEWS, searched using local headers
+ const nsMsgSearchScopeValue onlineManual = 13;
+ /// local news + junk
+ const nsMsgSearchScopeValue localNewsJunk = 14;
+ /// local news + body
+ const nsMsgSearchScopeValue localNewsBody = 15;
+ /// local news + junk + body
+ const nsMsgSearchScopeValue localNewsJunkBody = 16;
+};
+
+typedef long nsMsgSearchAttribValue;
+
+/**
+ * Definitions of search attribute types. The numerical order
+ * from here will also be used to determine the order that the
+ * attributes display in the filter editor.
+ */
+[scriptable, uuid(a83ca7e8-4591-4111-8fb8-fd76ac73c866)]
+interface nsMsgSearchAttrib : nsISupports {
+ const nsMsgSearchAttribValue Custom = -2; /* a custom term, see nsIMsgSearchCustomTerm */
+ const nsMsgSearchAttribValue Default = -1;
+ const nsMsgSearchAttribValue Subject = 0; /* mail and news */
+ const nsMsgSearchAttribValue Sender = 1;
+ const nsMsgSearchAttribValue Body = 2;
+ const nsMsgSearchAttribValue Date = 3;
+
+ const nsMsgSearchAttribValue Priority = 4; /* mail only */
+ const nsMsgSearchAttribValue MsgStatus = 5;
+ const nsMsgSearchAttribValue To = 6;
+ const nsMsgSearchAttribValue CC = 7;
+ const nsMsgSearchAttribValue ToOrCC = 8;
+ const nsMsgSearchAttribValue AllAddresses = 9;
+
+ const nsMsgSearchAttribValue Location = 10; /* result list only */
+ const nsMsgSearchAttribValue MessageKey = 11; /* message result elems */
+ const nsMsgSearchAttribValue AgeInDays = 12;
+ const nsMsgSearchAttribValue FolderInfo = 13; /* for "view thread context" from result */
+ const nsMsgSearchAttribValue Size = 14;
+ const nsMsgSearchAttribValue AnyText = 15;
+ const nsMsgSearchAttribValue Keywords = 16; // keywords are the internal representation of tags.
+
+ const nsMsgSearchAttribValue Name = 17;
+ const nsMsgSearchAttribValue DisplayName = 18;
+ const nsMsgSearchAttribValue Nickname = 19;
+ const nsMsgSearchAttribValue ScreenName = 20;
+ const nsMsgSearchAttribValue Email = 21;
+ const nsMsgSearchAttribValue AdditionalEmail = 22;
+ const nsMsgSearchAttribValue PhoneNumber = 23;
+ const nsMsgSearchAttribValue WorkPhone = 24;
+ const nsMsgSearchAttribValue HomePhone = 25;
+ const nsMsgSearchAttribValue Fax = 26;
+ const nsMsgSearchAttribValue Pager = 27;
+ const nsMsgSearchAttribValue Mobile = 28;
+ const nsMsgSearchAttribValue City = 29;
+ const nsMsgSearchAttribValue Street = 30;
+ const nsMsgSearchAttribValue Title = 31;
+ const nsMsgSearchAttribValue Organization = 32;
+ const nsMsgSearchAttribValue Department = 33;
+
+ // 34 - 43, reserved for ab / LDAP;
+ const nsMsgSearchAttribValue HasAttachmentStatus = 44;
+ const nsMsgSearchAttribValue JunkStatus = 45;
+ const nsMsgSearchAttribValue JunkPercent = 46;
+ const nsMsgSearchAttribValue JunkScoreOrigin = 47;
+ const nsMsgSearchAttribValue HdrProperty = 49; // uses nsIMsgSearchTerm::hdrProperty
+ const nsMsgSearchAttribValue FolderFlag = 50; // uses nsIMsgSearchTerm::status
+ const nsMsgSearchAttribValue Uint32HdrProperty = 51; // uses nsIMsgSearchTerm::hdrProperty
+
+ // 52 is for showing customize - in ui headers start from 53 onwards up until 99.
+
+ /** OtherHeader MUST ALWAYS BE LAST attribute since
+ * we can have an arbitrary # of these. The number can be changed,
+ * however, because we never persist AttribValues as integers.
+ */
+ const nsMsgSearchAttribValue OtherHeader = 52;
+ // must be last attribute
+ const nsMsgSearchAttribValue kNumMsgSearchAttributes = 100;
+};
+
+typedef long nsMsgSearchOpValue;
+
+[scriptable, uuid(9160b196-6fcb-4eba-aaaf-6c806c4ee420)]
+interface nsMsgSearchOp : nsISupports {
+ const nsMsgSearchOpValue Contains = 0; /* for text attributes */
+ const nsMsgSearchOpValue DoesntContain = 1;
+ const nsMsgSearchOpValue Is = 2; /* is and isn't also apply to some non-text attrs */
+ const nsMsgSearchOpValue Isnt = 3;
+ const nsMsgSearchOpValue IsEmpty = 4;
+
+ const nsMsgSearchOpValue IsBefore = 5; /* for date attributes */
+ const nsMsgSearchOpValue IsAfter = 6;
+
+ const nsMsgSearchOpValue IsHigherThan = 7; /* for priority. Is also applies */
+ const nsMsgSearchOpValue IsLowerThan = 8;
+
+ const nsMsgSearchOpValue BeginsWith = 9;
+ const nsMsgSearchOpValue EndsWith = 10;
+
+ const nsMsgSearchOpValue SoundsLike = 11; /* for LDAP phoenetic matching */
+ const nsMsgSearchOpValue LdapDwim = 12; /* Do What I Mean for simple search */
+
+ const nsMsgSearchOpValue IsGreaterThan = 13;
+ const nsMsgSearchOpValue IsLessThan = 14;
+
+ const nsMsgSearchOpValue NameCompletion = 15; /* Name Completion operator...as the name implies =) */
+ const nsMsgSearchOpValue IsInAB = 16;
+ const nsMsgSearchOpValue IsntInAB = 17;
+ const nsMsgSearchOpValue IsntEmpty = 18; /* primarily for tags */
+ const nsMsgSearchOpValue Matches = 19; /* generic term for use by custom terms */
+ const nsMsgSearchOpValue DoesntMatch = 20; /* generic term for use by custom terms */
+ const nsMsgSearchOpValue kNumMsgSearchOperators = 21; /* must be last operator */
+};
+
+typedef long nsMsgSearchWidgetValue;
+
+/* FEs use this to help build the search dialog box */
+[scriptable,uuid(903dd2e8-304e-11d3-92e6-00a0c900d445)]
+interface nsMsgSearchWidget : nsISupports {
+ const nsMsgSearchWidgetValue Text = 0;
+ const nsMsgSearchWidgetValue Date = 1;
+ const nsMsgSearchWidgetValue Menu = 2;
+ const nsMsgSearchWidgetValue Int = 3; /* added to account for age in days which requires an integer field */
+ const nsMsgSearchWidgetValue None = 4;
+};
+
+typedef long nsMsgSearchBooleanOperator;
+
+[scriptable, uuid(a37f3f4a-304e-11d3-8f94-00a0c900d445)]
+interface nsMsgSearchBooleanOp : nsISupports {
+ const nsMsgSearchBooleanOperator BooleanOR = 0;
+ const nsMsgSearchBooleanOperator BooleanAND = 1;
+};
+
+/* Use this to specify the value of a search term */
+
+[ptr] native nsMsgSearchValue(nsMsgSearchValue);
+
+%{C++
+#include "nsString.h"
+typedef struct nsMsgSearchValue
+{
+ nsMsgSearchAttribValue attribute;
+ union
+ {
+ nsMsgPriorityValue priority;
+ PRTime date;
+ uint32_t msgStatus; /* see MSG_FLAG in msgcom.h */
+ uint32_t size;
+ nsMsgKey key;
+ int32_t age; /* in days */
+ nsIMsgFolder *folder;
+ uint32_t junkStatus;
+ uint32_t junkPercent;
+ } u;
+ // We keep two versions of the string to avoid conversion at "search time".
+ nsCString utf8String;
+ nsString utf16String;
+} nsMsgSearchValue;
+%}
+
+[ptr] native nsMsgSearchTerm(nsMsgSearchTerm);
+
+// Please note the ! at the start of this macro, which means the macro
+// needs to enumerate the non-string attributes.
+%{C++
+#define IS_STRING_ATTRIBUTE(_a) \
+(!(_a == nsMsgSearchAttrib::Priority || _a == nsMsgSearchAttrib::Date || \
+ _a == nsMsgSearchAttrib::MsgStatus || _a == nsMsgSearchAttrib::MessageKey || \
+ _a == nsMsgSearchAttrib::Size || _a == nsMsgSearchAttrib::AgeInDays || \
+ _a == nsMsgSearchAttrib::FolderInfo || _a == nsMsgSearchAttrib::Location || \
+ _a == nsMsgSearchAttrib::JunkStatus || \
+ _a == nsMsgSearchAttrib::FolderFlag || _a == nsMsgSearchAttrib::Uint32HdrProperty || \
+ _a == nsMsgSearchAttrib::JunkPercent || _a == nsMsgSearchAttrib::HasAttachmentStatus))
+%}
+
+[ptr] native nsSearchMenuItem(nsSearchMenuItem);
diff --git a/comm/mailnews/search/public/nsMsgSearchScopeTerm.h b/comm/mailnews/search/public/nsMsgSearchScopeTerm.h
new file mode 100644
index 0000000000..b76cc676dc
--- /dev/null
+++ b/comm/mailnews/search/public/nsMsgSearchScopeTerm.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 __nsMsgSearchScopeTerm_h
+#define __nsMsgSearchScopeTerm_h
+
+#include "nsMsgSearchCore.h"
+#include "nsMsgSearchScopeTerm.h"
+#include "nsIMsgSearchAdapter.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgSearchAdapter.h"
+#include "nsIMsgSearchSession.h"
+#include "nsCOMPtr.h"
+#include "nsIWeakReferenceUtils.h"
+
+class nsMsgSearchScopeTerm : public nsIMsgSearchScopeTerm {
+ public:
+ nsMsgSearchScopeTerm(nsIMsgSearchSession*, nsMsgSearchScopeValue,
+ nsIMsgFolder*);
+ nsMsgSearchScopeTerm();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGSEARCHSCOPETERM
+
+ nsresult TimeSlice(bool* aDone);
+ nsresult InitializeAdapter(
+ nsTArray<RefPtr<nsIMsgSearchTerm>> const& termList);
+
+ char* GetStatusBarName();
+
+ nsMsgSearchScopeValue m_attribute;
+ nsCOMPtr<nsIMsgFolder> m_folder;
+ nsCOMPtr<nsIMsgSearchAdapter> m_adapter;
+ nsCOMPtr<nsIInputStream> m_inputStream; // for message bodies
+ nsWeakPtr m_searchSession;
+ bool m_searchServer;
+
+ private:
+ virtual ~nsMsgSearchScopeTerm();
+};
+
+#endif
diff --git a/comm/mailnews/search/public/nsMsgSearchTerm.h b/comm/mailnews/search/public/nsMsgSearchTerm.h
new file mode 100644
index 0000000000..2ab5618cad
--- /dev/null
+++ b/comm/mailnews/search/public/nsMsgSearchTerm.h
@@ -0,0 +1,89 @@
+/* -*- 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 __nsMsgSearchTerm_h
+#define __nsMsgSearchTerm_h
+//---------------------------------------------------------------------------
+// nsMsgSearchTerm specifies one criterion, e.g. name contains phil
+//---------------------------------------------------------------------------
+#include "nsIMsgSearchSession.h"
+#include "nsIMsgSearchScopeTerm.h"
+#include "nsIMsgSearchTerm.h"
+#include "nsIMsgSearchCustomTerm.h"
+
+// needed to search for addresses in address books
+#include "nsIAbDirectory.h"
+
+#define EMPTY_MESSAGE_LINE(buf) \
+ (buf[0] == '\r' || buf[0] == '\n' || buf[0] == '\0')
+
+class nsMsgSearchTerm : public nsIMsgSearchTerm {
+ public:
+ nsMsgSearchTerm();
+ nsMsgSearchTerm(nsMsgSearchAttribValue, nsMsgSearchOpValue,
+ nsIMsgSearchValue*, nsMsgSearchBooleanOperator,
+ const char* arbitraryHeader);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGSEARCHTERM
+
+ nsresult DeStream(char*, int16_t length);
+ nsresult DeStreamNew(char*, int16_t length);
+
+ nsresult GetLocalTimes(PRTime, PRTime, PRExplodedTime&, PRExplodedTime&);
+
+ bool IsBooleanOpAND() {
+ return m_booleanOp == nsMsgSearchBooleanOp::BooleanAND ? true : false;
+ }
+ nsMsgSearchBooleanOperator GetBooleanOp() { return m_booleanOp; }
+ // maybe should return nsString & ??
+ const char* GetArbitraryHeader() { return m_arbitraryHeader.get(); }
+
+ static char* EscapeQuotesInStr(const char* str);
+
+ nsMsgSearchAttribValue m_attribute;
+ nsMsgSearchOpValue m_operator;
+ nsMsgSearchValue m_value;
+
+ // boolean operator to be applied to this search term and the search term
+ // which precedes it.
+ nsMsgSearchBooleanOperator m_booleanOp;
+
+ // user specified string for the name of the arbitrary header to be used in
+ // the search only has a value when m_attribute = OtherHeader!!!!
+ nsCString m_arbitraryHeader;
+
+ // db hdr property name to use - used when m_attribute = HdrProperty.
+ nsCString m_hdrProperty;
+ bool m_matchAll; // does this term match all headers?
+ nsCString m_customId; // id of custom search term
+
+ protected:
+ virtual ~nsMsgSearchTerm();
+
+ nsresult MatchString(const nsACString& stringToMatch, const char* charset,
+ bool* pResult);
+ nsresult MatchString(const nsAString& stringToMatch, bool* pResult);
+ nsresult OutputValue(nsCString& outputStr);
+ nsresult ParseAttribute(char* inStream, nsMsgSearchAttribValue* attrib);
+ nsresult ParseOperator(char* inStream, nsMsgSearchOpValue* value);
+ nsresult ParseValue(char* inStream);
+ /**
+ * Switch a string to lower case, except for special database rows
+ * that are not headers, but could be headers
+ *
+ * @param aValue the string to switch
+ */
+ void ToLowerCaseExceptSpecials(nsACString& aValue);
+ nsresult InitializeAddressBook();
+ nsresult MatchInAddressBook(const nsAString& aAddress, bool* pResult);
+ // fields used by search in address book
+ nsCOMPtr<nsIAbDirectory> mDirectory;
+
+ bool mBeginsGrouping;
+ bool mEndsGrouping;
+};
+
+#endif
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
diff --git a/comm/mailnews/search/test/moz.build b/comm/mailnews/search/test/moz.build
new file mode 100644
index 0000000000..6b37fdbe09
--- /dev/null
+++ b/comm/mailnews/search/test/moz.build
@@ -0,0 +1,6 @@
+# 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/.
+
+XPCSHELL_TESTS_MANIFESTS += ["unit/xpcshell.ini"]
diff --git a/comm/mailnews/search/test/unit/head_mailbase.js b/comm/mailnews/search/test/unit/head_mailbase.js
new file mode 100644
index 0000000000..9f37623291
--- /dev/null
+++ b/comm/mailnews/search/test/unit/head_mailbase.js
@@ -0,0 +1,23 @@
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+var { mailTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MailTestUtils.jsm"
+);
+var { localAccountUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/LocalAccountUtils.jsm"
+);
+
+var CC = Components.Constructor;
+
+// Ensure the profile directory is set up
+do_get_profile();
+
+var gDEPTH = "../../../../";
+
+registerCleanupFunction(function () {
+ load(gDEPTH + "mailnews/resources/mailShutdown.js");
+});
diff --git a/comm/mailnews/search/test/unit/test_base64_decoding.js b/comm/mailnews/search/test/unit/test_base64_decoding.js
new file mode 100644
index 0000000000..e0f7ddd94d
--- /dev/null
+++ b/comm/mailnews/search/test/unit/test_base64_decoding.js
@@ -0,0 +1,94 @@
+/* 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 tests that we do not crash when loading the email bodySearchCrash,
+// which was fixed in bug 465805
+
+/* import-globals-from ../../../test/resources/searchTestUtils.js */
+load("../../../resources/searchTestUtils.js");
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var Contains = Ci.nsMsgSearchOp.Contains;
+var Body = Ci.nsMsgSearchAttrib.Body;
+
+var Files = [
+ "../../../data/bugmail1",
+ "../../../data/bodySearchCrash", // Test for bug 465805.
+ "../../../data/base64-with-whitespace.eml", // Test for bug 1487421.
+];
+
+var Tests = [
+ {
+ // this number appears in bugmail1
+ value: "432710",
+ attrib: Body,
+ op: Contains,
+ count: 1,
+ },
+ {
+ // this appears in base64-with-whitespace.eml
+ value: "abcdefghijklmnopqrstuvwxyz",
+ attrib: Body,
+ op: Contains,
+ count: 1,
+ },
+];
+
+function run_test() {
+ // Setup local mail accounts.
+ localAccountUtils.loadLocalMailAccount();
+
+ // Get a message into the local filestore. function testBodySearch() continues the testing after the copy.
+ do_test_pending();
+ copyListener.OnStopCopy(null);
+ return true;
+}
+
+var copyListener = {
+ OnStartCopy() {},
+ OnProgress(aProgress, aProgressMax) {},
+ SetMessageKey(aKey) {},
+ SetMessageId(aMessageId) {},
+ OnStopCopy(aStatus) {
+ let fileName = Files.shift();
+ if (fileName) {
+ let file = do_get_file(fileName);
+ MailServices.copy.copyFileMessage(
+ file,
+ localAccountUtils.inboxFolder,
+ null,
+ false,
+ 0,
+ "",
+ copyListener,
+ null
+ );
+ } else {
+ testBodySearch();
+ }
+ },
+};
+
+// Runs at completion of copy
+
+// process each test from queue, calls itself upon completion of each search
+function testBodySearch() {
+ print("Test Body Search");
+ var test = Tests.shift();
+ if (test) {
+ new TestSearch(
+ localAccountUtils.inboxFolder,
+ test.value,
+ test.attrib,
+ test.op,
+ test.count,
+ testBodySearch
+ );
+ } else {
+ do_test_finished();
+ }
+}
diff --git a/comm/mailnews/search/test/unit/test_bug366491.js b/comm/mailnews/search/test/unit/test_bug366491.js
new file mode 100644
index 0000000000..774e8932a3
--- /dev/null
+++ b/comm/mailnews/search/test/unit/test_bug366491.js
@@ -0,0 +1,110 @@
+/* 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/. */
+
+// tests return of junk percent from bayesian filter
+
+// main setup
+
+// only needed during debug
+// do_import_script("mailnews/extensions/bayesian-spam-filter/test/resources/trainingfile.js");
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+// local constants
+var kUnclassified = MailServices.junk.UNCLASSIFIED;
+var kJunk = MailServices.junk.JUNK;
+var kGood = MailServices.junk.GOOD;
+
+/*
+ * This test is not intended to check the spam calculations,
+ * but only that the junk percent is transmitted (particularly
+ * for intermediate values). The test
+ * junkPercent values below were calculated by the plugin,
+ * not indepedently verified.
+ */
+
+var tests = [
+ {
+ fileName: "ham2.eml",
+ junkPercent: 8,
+ },
+ {
+ fileName: "spam2.eml",
+ junkPercent: 81,
+ },
+];
+
+var emails = [
+ {
+ fileName: "ham1.eml",
+ classification: kGood,
+ },
+ {
+ fileName: "spam1.eml",
+ classification: kJunk,
+ },
+];
+
+// main test
+function run_test() {
+ localAccountUtils.loadLocalMailAccount();
+ do_test_pending();
+ doTestingListener.onMessageClassified(null, null, null);
+ return true;
+}
+
+var haveClassification = false;
+var doTestingListener = {
+ onMessageClassified(aMsgURI, aClassification, aJunkPercent) {
+ // Do we have more training emails? If so, train
+ var email = emails.shift();
+ if (email) {
+ MailServices.junk.setMessageClassification(
+ getSpec(email.fileName),
+ kUnclassified,
+ email.classification,
+ null,
+ doTestingListener
+ );
+ return;
+ }
+
+ if (!aMsgURI) {
+ // Ignore end of batch.
+ return;
+ }
+
+ // Have we completed a classification? If so, test
+ if (haveClassification) {
+ let test = tests.shift();
+ Assert.equal(getSpec(test.fileName), aMsgURI);
+ Assert.equal(test.junkPercent, aJunkPercent);
+ }
+
+ // Do we have more classifications to do? Then classify the first one.
+ if (tests.length) {
+ haveClassification = true;
+ MailServices.junk.classifyMessage(
+ getSpec(tests[0].fileName),
+ null,
+ doTestingListener
+ );
+ } else {
+ do_test_finished();
+ }
+ },
+};
+
+// helper functions
+
+function getSpec(aFileName) {
+ var file = do_get_file(
+ "../../../extensions/bayesian-spam-filter/test/unit/resources/" + aFileName
+ );
+ var uri = Services.io.newFileURI(file).QueryInterface(Ci.nsIURL);
+ uri = uri.mutate().setQuery("type=application/x-message-display").finalize();
+ return uri.spec;
+}
diff --git a/comm/mailnews/search/test/unit/test_bug404489.js b/comm/mailnews/search/test/unit/test_bug404489.js
new file mode 100644
index 0000000000..93ae2aac3c
--- /dev/null
+++ b/comm/mailnews/search/test/unit/test_bug404489.js
@@ -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/. */
+
+// Tests that custom headers like "Sender" work (bug 404489)
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var Contains = Ci.nsMsgSearchOp.Contains;
+var gArrayHdrs = ["X-Bugzilla-Who", "Sender"];
+var gFirstHeader = Ci.nsMsgSearchAttrib.OtherHeader + 1;
+var fileName = "../../../data/SenderHeader";
+
+var Tests = [
+ /* test header:
+ X-Bugzilla-Who: bugmail@example.org
+
+ This just shows that normal custom headers work
+ */
+ {
+ testValue: "bugmail",
+ attrib: gFirstHeader,
+ op: Contains,
+ count: 1,
+ },
+ {
+ testValue: "ThisIsNotThere",
+ attrib: gFirstHeader,
+ op: Contains,
+ count: 0,
+ },
+ /* test header:
+ Sender: iamthesender@example.com
+
+ This is the main fix of bug 404489, that we can use Sender as a header
+ */
+ {
+ testValue: "iamthesender",
+ attrib: gFirstHeader + 1,
+ op: Contains,
+ count: 1,
+ },
+ /* test header:
+ From: bugzilla-daemon@mozilla.invalid
+
+ Here we show that the "From" header does not fire tests for the
+ "Sender" arbitrary headers, but does fire the standard test
+ for nsMsgSenderAttrib.Sender
+ */
+ {
+ testValue: "bugzilla",
+ attrib: gFirstHeader + 1,
+ op: Contains,
+ count: 0,
+ },
+ {
+ testValue: "bugzilla",
+ attrib: Ci.nsMsgSearchAttrib.Sender,
+ op: Contains,
+ count: 1,
+ },
+];
+
+function run_test() {
+ localAccountUtils.loadLocalMailAccount();
+
+ // add the custom headers into the preferences file, ":" delimited
+
+ var hdrs;
+ if (gArrayHdrs.length == 1) {
+ hdrs = gArrayHdrs;
+ } else {
+ hdrs = gArrayHdrs.join(": ");
+ }
+ Services.prefs.setCharPref("mailnews.customHeaders", hdrs);
+
+ // Get a message into the local filestore. function continue_test() continues the testing after the copy.
+ do_test_pending();
+ var file = do_get_file(fileName);
+ MailServices.copy.copyFileMessage(
+ file,
+ localAccountUtils.inboxFolder,
+ null,
+ false,
+ 0,
+ "",
+ copyListener,
+ null
+ );
+ return true;
+}
+
+var copyListener = {
+ OnStartCopy() {},
+ OnProgress(aProgress, aProgressMax) {},
+ SetMessageKey(aKey) {},
+ SetMessageId(aMessageId) {},
+ OnStopCopy(aStatus) {
+ continue_test();
+ },
+};
+
+// Runs at completion of each copy
+// process each test from queue, calls itself upon completion of each search
+function continue_test() {
+ var test = Tests.shift();
+ if (test) {
+ new TestSearchx(
+ localAccountUtils.inboxFolder,
+ test.testValue,
+ test.attrib,
+ test.op,
+ test.count,
+ continue_test
+ );
+ } else {
+ do_test_finished();
+ }
+}
+
+/*
+ * TestSearchx: Class to test number of search hits
+ *
+ * @param aFolder: the folder to search
+ * @param aValue: value used for the search
+ * The interpretation of aValue depends on aAttrib. It
+ * defaults to string, but for certain attributes other
+ * types are used.
+ * WARNING: not all attributes have been tested.
+ *
+ * @param aAttrib: attribute for the search (Ci.nsMsgSearchAttrib.Size, etc.)
+ * @param aOp: operation for the search (Ci.nsMsgSearchOp.Contains, etc.)
+ * @param aHitCount: expected number of search hits
+ * @param onDone: function to call on completion of search
+ *
+ */
+
+function TestSearchx(aFolder, aValue, aAttrib, aOp, aHitCount, onDone) {
+ var searchListener = {
+ onSearchHit(dbHdr, folder) {
+ hitCount++;
+ },
+ onSearchDone(status) {
+ print("Finished search does " + aHitCount + " equal " + hitCount + "?");
+ searchSession = null;
+ Assert.equal(aHitCount, hitCount);
+ if (onDone) {
+ onDone();
+ }
+ },
+ onNewSearch() {
+ hitCount = 0;
+ },
+ };
+
+ // define and initiate the search session
+
+ var hitCount;
+ var searchSession = Cc[
+ "@mozilla.org/messenger/searchSession;1"
+ ].createInstance(Ci.nsIMsgSearchSession);
+ searchSession.addScopeTerm(Ci.nsMsgSearchScope.offlineMail, aFolder);
+ var searchTerm = searchSession.createTerm();
+ searchTerm.attrib = aAttrib;
+
+ var value = searchTerm.value;
+ // This is tricky - value.attrib must be set before actual values
+ value.attrib = aAttrib;
+ if (aAttrib == Ci.nsMsgSearchAttrib.JunkPercent) {
+ value.junkPercent = aValue;
+ } else if (aAttrib == Ci.nsMsgSearchAttrib.Priority) {
+ value.priority = aValue;
+ } else if (aAttrib == Ci.nsMsgSearchAttrib.Date) {
+ value.date = aValue;
+ } else if (aAttrib == Ci.nsMsgSearchAttrib.MsgStatus) {
+ value.status = aValue;
+ } else if (aAttrib == Ci.nsMsgSearchAttrib.MessageKey) {
+ value.msgKey = aValue;
+ } else if (aAttrib == Ci.nsMsgSearchAttrib.Size) {
+ value.size = aValue;
+ } else if (aAttrib == Ci.nsMsgSearchAttrib.AgeInDays) {
+ value.age = aValue;
+ } else if (aAttrib == Ci.nsMsgSearchAttrib.JunkStatus) {
+ value.junkStatus = aValue;
+ } else if (aAttrib == Ci.nsMsgSearchAttrib.HasAttachmentStatus) {
+ value.status = Ci.nsMsgMessageFlags.Attachment;
+ } else {
+ value.str = aValue;
+ }
+ searchTerm.value = value;
+ if (aAttrib > Ci.nsMsgSearchAttrib.OtherHeader) {
+ searchTerm.arbitraryHeader =
+ gArrayHdrs[aAttrib - 1 - Ci.nsMsgSearchAttrib.OtherHeader];
+ }
+ searchTerm.op = aOp;
+ searchTerm.booleanAnd = false;
+ searchSession.appendTerm(searchTerm);
+ searchSession.registerListener(searchListener);
+ searchSession.search(null);
+}
diff --git a/comm/mailnews/search/test/unit/test_copyThenMoveManual.js b/comm/mailnews/search/test/unit/test_copyThenMoveManual.js
new file mode 100644
index 0000000000..ef34aeb3ca
--- /dev/null
+++ b/comm/mailnews/search/test/unit/test_copyThenMoveManual.js
@@ -0,0 +1,116 @@
+/*
+ * This file tests copy followed by a move in a single filter.
+ * Tests fix from bug 448337.
+ *
+ * Original author: Kent James <kent@caspia.com>
+ */
+
+/* import-globals-from ../../../test/resources/POP3pump.js */
+load("../../../resources/POP3pump.js");
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+const { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gFiles = ["../../../data/bugmail1"];
+var gCopyFolder;
+var gMoveFolder;
+var gFilter; // the test filter
+var gFilterList;
+var gTestArray = [
+ function createFilters() {
+ // setup manual copy then move mail filters on the inbox
+ gFilterList = localAccountUtils.incomingServer.getFilterList(null);
+ gFilter = gFilterList.createFilter("copyThenMoveAll");
+ let searchTerm = gFilter.createTerm();
+ searchTerm.matchAll = true;
+ gFilter.appendTerm(searchTerm);
+ let copyAction = gFilter.createAction();
+ copyAction.type = Ci.nsMsgFilterAction.CopyToFolder;
+ copyAction.targetFolderUri = gCopyFolder.URI;
+ gFilter.appendAction(copyAction);
+ let moveAction = gFilter.createAction();
+ moveAction.type = Ci.nsMsgFilterAction.MoveToFolder;
+ moveAction.targetFolderUri = gMoveFolder.URI;
+ gFilter.appendAction(moveAction);
+ gFilter.enabled = true;
+ gFilter.filterType = Ci.nsMsgFilterType.Manual;
+ gFilterList.insertFilterAt(0, gFilter);
+ },
+ // just get a message into the local folder
+ async function getLocalMessages1() {
+ gPOP3Pump.files = gFiles;
+ await gPOP3Pump.run();
+ },
+ // test applying filters to a message header
+ async function applyFilters() {
+ let promiseFolderEvent = PromiseTestUtils.promiseFolderEvent(
+ localAccountUtils.inboxFolder,
+ "DeleteOrMoveMsgCompleted"
+ );
+ MailServices.filters.applyFilters(
+ Ci.nsMsgFilterType.Manual,
+ [localAccountUtils.inboxFolder.firstNewMessage],
+ localAccountUtils.inboxFolder,
+ null
+ );
+ await promiseFolderEvent;
+ },
+ function verifyFolders1() {
+ // Copy and Move should each now have 1 message in them.
+ Assert.equal(folderCount(gCopyFolder), 1);
+ Assert.equal(folderCount(gMoveFolder), 1);
+ // the local inbox folder should now be empty, since the second
+ // operation was a move
+ Assert.equal(folderCount(localAccountUtils.inboxFolder), 0);
+ },
+ // just get a message into the local folder
+ async function getLocalMessages2() {
+ gPOP3Pump.files = gFiles;
+ await gPOP3Pump.run();
+ },
+ // use the alternate call into the filter service
+ async function applyFiltersToFolders() {
+ let folders = [localAccountUtils.inboxFolder];
+ let promiseFolderEvent = PromiseTestUtils.promiseFolderEvent(
+ localAccountUtils.inboxFolder,
+ "DeleteOrMoveMsgCompleted"
+ );
+ MailServices.filters.applyFiltersToFolders(gFilterList, folders, null);
+ await promiseFolderEvent;
+ },
+ function verifyFolders2() {
+ // Copy and Move should each now have 2 message in them.
+ Assert.equal(folderCount(gCopyFolder), 2);
+ Assert.equal(folderCount(gMoveFolder), 2);
+ // the local inbox folder should now be empty, since the second
+ // operation was a move
+ Assert.equal(folderCount(localAccountUtils.inboxFolder), 0);
+ },
+ function endTest() {
+ // Cleanup, null out everything, close all cached connections and stop the
+ // server
+ dump(" Exiting mail tests\n");
+ gPOP3Pump = null;
+ },
+];
+
+function folderCount(folder) {
+ return [...folder.msgDatabase.enumerateMessages()].length;
+}
+
+function run_test() {
+ if (!localAccountUtils.inboxFolder) {
+ localAccountUtils.loadLocalMailAccount();
+ }
+
+ gCopyFolder = localAccountUtils.rootFolder.createLocalSubfolder("CopyFolder");
+ gMoveFolder = localAccountUtils.rootFolder.createLocalSubfolder("MoveFolder");
+
+ gTestArray.forEach(x => add_task(x));
+
+ run_next_test();
+}
diff --git a/comm/mailnews/search/test/unit/test_junkWhitelisting.js b/comm/mailnews/search/test/unit/test_junkWhitelisting.js
new file mode 100644
index 0000000000..9263de68a2
--- /dev/null
+++ b/comm/mailnews/search/test/unit/test_junkWhitelisting.js
@@ -0,0 +1,204 @@
+/* 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/. */
+
+/*
+ * Testing of junk whitelisting
+ */
+
+// add address book setup
+/* import-globals-from ../../../test/resources/abSetup.js */
+load("../../../resources/abSetup.js");
+
+// add fake POP3 server driver
+/* import-globals-from ../../../test/resources/POP3pump.js */
+load("../../../resources/POP3pump.js");
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+/*
+ * The address available in the test address book is "PrimaryEmail1@test.invalid"
+ * Test emails may also include the address "invalid@example.com"
+ *
+ * Map of test email contents: (P is "Prim...", I is "inva.." address)
+ *
+ * Index Bugmail# From
+ * 0 1 P
+ * 1 3 I
+ *
+ */
+
+// indices into hdrs[] of email by domain
+var kDomainTest = 0;
+var kDomainExample = 1;
+
+var Files = ["../../../data/bugmail1", "../../../data/bugmail3"];
+
+var hdrs = [];
+
+function run_test() {
+ loadABFile(
+ "../../../addrbook/test/unit/data/cardForEmail",
+ kPABData.fileName
+ );
+
+ do_test_pending();
+
+ // kick off copying
+ gPOP3Pump.files = Files;
+ gPOP3Pump.onDone = continueTest;
+ gPOP3Pump.run();
+}
+
+function continueTest() {
+ // get the message headers
+ for (let header of localAccountUtils.inboxFolder.messages) {
+ hdrs.push(header);
+ }
+
+ // check with spam properties set on the local server
+ doChecks(localAccountUtils.incomingServer);
+
+ // Free our globals
+ hdrs = null;
+ gPOP3Pump = null;
+ do_test_finished();
+}
+
+function doChecks(server) {
+ let spamSettings = server.spamSettings;
+
+ // default is to use the whitelist
+ Assert.ok(spamSettings.useWhiteList);
+
+ // check email with the address PrimaryEmail1@test.invalid
+ Assert.ok(spamSettings.checkWhiteList(hdrs[kDomainTest]));
+
+ // check email without the address
+ Assert.ok(!spamSettings.checkWhiteList(hdrs[kDomainExample]));
+
+ //
+ // check changes in server-level settings. Although the spamSettings object
+ // has methods to set these, those methods are not persistent (which seems
+ // strange). You need to set the actual preference, and call initialize on
+ // spam settings, to get the settings to be saved persistently and stick, then
+ // be recalled into the program. So that's the way that I will test it.
+ //
+
+ // disable whitelisting
+ server.setBoolValue("useWhiteList", false);
+ spamSettings.initialize(server);
+
+ // check that the change was propagated to spamSettings
+ Assert.ok(!spamSettings.useWhiteList);
+
+ // and affects whitelisting calculationss
+ Assert.ok(!spamSettings.checkWhiteList(hdrs[kDomainTest]));
+
+ // re-enable whitelisting
+ server.setBoolValue("useWhiteList", true);
+ spamSettings.initialize(server);
+ Assert.ok(spamSettings.checkWhiteList(hdrs[kDomainTest]));
+
+ // Set an empty white list.
+ // To really empty this, I have to change the default value as well
+ Services.prefs.setCharPref("mail.server.default.whiteListAbURI", "");
+ server.setCharValue("whiteListAbURI", "");
+ spamSettings.initialize(server);
+ Assert.ok(!spamSettings.checkWhiteList(hdrs[kDomainTest]));
+
+ // add a trusted domain. This is a global preference
+ Services.prefs.setCharPref("mail.trusteddomains", "example.com");
+ spamSettings.initialize(server);
+
+ // check email with the address invalid@example.com, a trusted domain
+ Assert.ok(spamSettings.checkWhiteList(hdrs[kDomainExample]));
+
+ // check email without the address
+ Assert.ok(!spamSettings.checkWhiteList(hdrs[kDomainTest]));
+
+ // disable the trusted domain
+ Services.prefs.setCharPref("mail.trusteddomains", "");
+ spamSettings.initialize(server);
+ Assert.ok(!spamSettings.checkWhiteList(hdrs[kDomainExample]));
+
+ // add back the Personal Address Book
+ server.setCharValue("whiteListAbURI", kPABData.URI);
+ spamSettings.initialize(server);
+ Assert.ok(spamSettings.checkWhiteList(hdrs[kDomainTest]));
+
+ /*
+ * tests of whitelist suppression by identity
+ */
+
+ // setup
+ let account = MailServices.accounts.FindAccountForServer(server);
+ let identity = MailServices.accounts.createIdentity();
+ // start with an email that does not match
+ identity.email = "iAmNotTheSender@test.invalid";
+ account.addIdentity(identity);
+
+ // setup account and identify for the deferred-from fake server
+ let fakeAccount = MailServices.accounts.createAccount();
+ fakeAccount.incomingServer = gPOP3Pump.fakeServer;
+ let fakeIdentity = MailServices.accounts.createIdentity();
+ // start with an email that does not match
+ fakeIdentity.email = "iAmNotTheSender@wrong.invalid";
+ fakeAccount.addIdentity(fakeIdentity);
+
+ // gPOP3Pump delivers messages to the local inbox regardless of other
+ // settings. But because we are testing here one of those other settings,
+ // let's just pretend that it works like the real POP3 stuff, and set
+ // the correct setting for deferring.
+ gPOP3Pump.fakeServer.setCharValue("deferred_to_account", "account1");
+
+ // suppress whitelisting for sender
+ server.setBoolValue("inhibitWhiteListingIdentityUser", true);
+ spamSettings.initialize(server);
+ // (email does not match yet though)
+ Assert.ok(spamSettings.checkWhiteList(hdrs[kDomainTest]));
+
+ // add a matching email (mixing case)
+ identity.email = "PrimaryEMAIL1@test.INVALID";
+ spamSettings.initialize(server);
+ Assert.ok(!spamSettings.checkWhiteList(hdrs[kDomainTest]));
+
+ // remove the matching email
+ identity.email = "iAmNotTheSender@test.invalid";
+ spamSettings.initialize(server);
+ Assert.ok(spamSettings.checkWhiteList(hdrs[kDomainTest]));
+
+ // add the email to the deferred-from server
+ fakeIdentity.email = "PrimaryEMAIL1@test.INVALID";
+ spamSettings.initialize(server);
+ Assert.ok(!spamSettings.checkWhiteList(hdrs[kDomainTest]));
+
+ // stop suppressing identity users
+ server.setBoolValue("inhibitWhiteListingIdentityUser", false);
+ spamSettings.initialize(server);
+ Assert.ok(spamSettings.checkWhiteList(hdrs[kDomainTest]));
+
+ // remove the matching email from the fake identity
+ fakeIdentity.email = "iAmNotTheSender@wrong.invalid";
+
+ // add a fully non-matching domain to the identity
+ identity.email = "PrimaryEmail1@wrong.invalid";
+
+ // suppress whitelist by matching domain
+ server.setBoolValue("inhibitWhiteListingIdentityDomain", true);
+ spamSettings.initialize(server);
+ // but domain still does not match
+ Assert.ok(spamSettings.checkWhiteList(hdrs[kDomainTest]));
+
+ // add a matching email to the identity, in the domain (mixing case)
+ identity.email = "iAmNotTheSender@TEST.invalid";
+ spamSettings.initialize(server);
+ Assert.ok(!spamSettings.checkWhiteList(hdrs[kDomainTest]));
+
+ // stop suppressing whitelist by domain
+ server.setBoolValue("inhibitWhiteListingIdentityDomain", false);
+ spamSettings.initialize(server);
+ Assert.ok(spamSettings.checkWhiteList(hdrs[kDomainTest]));
+}
diff --git a/comm/mailnews/search/test/unit/test_quarantineFilterMove.js b/comm/mailnews/search/test/unit/test_quarantineFilterMove.js
new file mode 100644
index 0000000000..853a3980f4
--- /dev/null
+++ b/comm/mailnews/search/test/unit/test_quarantineFilterMove.js
@@ -0,0 +1,181 @@
+/* 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/. */
+
+/*
+ * tests message moves with filter and quarantine enabled per bug 582918.
+ * It then tests that subsequent moves of the filtered messages work.
+ *
+ * adapted from test_copyThenMoveManual.js
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+const { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+/* import-globals-from ../../../test/resources/POP3pump.js */
+load("../../../resources/POP3pump.js");
+
+var gFiles = ["../../../data/bugmail1", "../../../data/bugmail10"];
+
+var gMoveFolder, gMoveFolder2;
+var gFilter; // the test filter
+var gFilterList;
+var gTestArray = [
+ function createFilters() {
+ gFilterList = gPOP3Pump.fakeServer.getFilterList(null);
+ gFilter = gFilterList.createFilter("MoveAll");
+ let searchTerm = gFilter.createTerm();
+ searchTerm.matchAll = true;
+ gFilter.appendTerm(searchTerm);
+ let moveAction = gFilter.createAction();
+ moveAction.type = Ci.nsMsgFilterAction.MoveToFolder;
+ moveAction.targetFolderUri = gMoveFolder.URI;
+ gFilter.appendAction(moveAction);
+ gFilter.enabled = true;
+ gFilter.filterType = Ci.nsMsgFilterType.InboxRule;
+ gFilterList.insertFilterAt(0, gFilter);
+ },
+ // just get a message into the local folder
+ async function getLocalMessages1() {
+ gPOP3Pump.files = gFiles;
+ let promise1 = PromiseTestUtils.promiseFolderNotification(
+ gMoveFolder,
+ "msgsClassified"
+ );
+ let promise2 = gPOP3Pump.run();
+ await Promise.all([promise1, promise2]);
+ },
+ async function verifyFolders1() {
+ Assert.equal(folderCount(gMoveFolder), 2);
+ // the local inbox folder should now be empty, since the second
+ // operation was a move
+ Assert.equal(folderCount(localAccountUtils.inboxFolder), 0);
+
+ let msgs = [...gMoveFolder.msgDatabase.enumerateMessages()];
+ let firstMsgHdr = msgs[0];
+ let secondMsgHdr = msgs[1];
+ // Check that the messages have content
+ let messageContent = await getContentFromMessage(firstMsgHdr);
+ Assert.ok(
+ messageContent.includes("Some User <bugmail@example.org> changed")
+ );
+ messageContent = await getContentFromMessage(secondMsgHdr);
+ Assert.ok(
+ messageContent.includes(
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=436880"
+ )
+ );
+ },
+ async function copyMovedMessages() {
+ let msgs = [...gMoveFolder.msgDatabase.enumerateMessages()];
+ let firstMsgHdr = msgs[0];
+ let secondMsgHdr = msgs[1];
+ let promiseCopyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyMessages(
+ gMoveFolder,
+ [firstMsgHdr, secondMsgHdr],
+ gMoveFolder2,
+ false,
+ promiseCopyListener,
+ null,
+ false
+ );
+ let promiseMoveMsg = PromiseTestUtils.promiseFolderEvent(
+ gMoveFolder,
+ "DeleteOrMoveMsgCompleted"
+ );
+ await Promise.all([promiseCopyListener.promise, promiseMoveMsg]);
+ },
+ async function verifyFolders2() {
+ Assert.equal(folderCount(gMoveFolder2), 2);
+
+ let msgs = [...gMoveFolder2.msgDatabase.enumerateMessages()];
+ let firstMsgHdr = msgs[0];
+ let secondMsgHdr = msgs[1];
+ // Check that the messages have content
+ let messageContent = await getContentFromMessage(firstMsgHdr);
+ Assert.ok(
+ messageContent.includes("Some User <bugmail@example.org> changed")
+ );
+ messageContent = await getContentFromMessage(secondMsgHdr);
+ Assert.ok(
+ messageContent.includes(
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=436880"
+ )
+ );
+ },
+ function endTest() {
+ dump("Exiting mail tests\n");
+ gPOP3Pump = null;
+ },
+];
+
+function folderCount(folder) {
+ return [...folder.msgDatabase.enumerateMessages()].length;
+}
+
+function run_test() {
+ /* may not work in Linux */
+ // if ("@mozilla.org/gnome-gconf-service;1" in Cc)
+ // return;
+ /**/
+ // quarantine messages
+ Services.prefs.setBoolPref("mailnews.downloadToTempFile", true);
+ if (!localAccountUtils.inboxFolder) {
+ localAccountUtils.loadLocalMailAccount();
+ }
+
+ gMoveFolder = localAccountUtils.rootFolder.createLocalSubfolder("MoveFolder");
+ gMoveFolder2 =
+ localAccountUtils.rootFolder.createLocalSubfolder("MoveFolder2");
+
+ gTestArray.forEach(x => add_task(x));
+ run_next_test();
+}
+
+/**
+ * Get the full message content.
+ *
+ * @param aMsgHdr - nsIMsgDBHdr object whose text body will be read.
+ * @returns {Promise<string>} full message contents.
+ */
+function getContentFromMessage(aMsgHdr) {
+ let msgFolder = aMsgHdr.folder;
+ let msgUri = msgFolder.getUriForMsg(aMsgHdr);
+
+ return new Promise((resolve, reject) => {
+ let streamListener = {
+ QueryInterface: ChromeUtils.generateQI(["nsIStreamListener"]),
+ sis: Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ ),
+ content: "",
+ onDataAvailable(request, inputStream, offset, count) {
+ this.sis.init(inputStream);
+ this.content += this.sis.read(count);
+ },
+ onStartRequest(request) {},
+ onStopRequest(request, statusCode) {
+ this.sis.close();
+ if (Components.isSuccessCode(statusCode)) {
+ resolve(this.content);
+ } else {
+ reject(new Error(statusCode));
+ }
+ },
+ };
+ MailServices.messageServiceFromURI(msgUri).streamMessage(
+ msgUri,
+ streamListener,
+ null,
+ null,
+ false,
+ "",
+ false
+ );
+ });
+}
diff --git a/comm/mailnews/search/test/unit/test_search.js b/comm/mailnews/search/test/unit/test_search.js
new file mode 100644
index 0000000000..98e8874fd3
--- /dev/null
+++ b/comm/mailnews/search/test/unit/test_search.js
@@ -0,0 +1,623 @@
+/* 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/. */
+
+/*
+ * Testing of general mail search features.
+ *
+ * This tests some search attributes not tested by other specific tests,
+ * e.g., test_searchTag.js or test_searchJunk.js
+ */
+/* import-globals-from ../../../test/resources/searchTestUtils.js */
+load("../../../resources/searchTestUtils.js");
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var Isnt = Ci.nsMsgSearchOp.Isnt;
+var Is = Ci.nsMsgSearchOp.Is;
+var Contains = Ci.nsMsgSearchOp.Contains;
+var DoesntContain = Ci.nsMsgSearchOp.DoesntContain;
+var BeginsWith = Ci.nsMsgSearchOp.BeginsWith;
+var EndsWith = Ci.nsMsgSearchOp.EndsWith;
+var IsBefore = Ci.nsMsgSearchOp.IsBefore; // control entry not enabled
+var IsAfter = Ci.nsMsgSearchOp.IsAfter;
+var IsHigherThan = Ci.nsMsgSearchOp.IsHigherThan;
+var IsLowerThan = Ci.nsMsgSearchOp.IsLowerThan;
+
+var OtherHeader = Ci.nsMsgSearchAttrib.OtherHeader;
+var From = Ci.nsMsgSearchAttrib.Sender;
+var Subject = Ci.nsMsgSearchAttrib.Subject;
+var Priority = Ci.nsMsgSearchAttrib.Priority;
+var SDate = Ci.nsMsgSearchAttrib.Date;
+
+var Tests = [
+ // test the To: header
+ {
+ testString: "PrimaryEmail1@test.invalid",
+ testAttribute: From,
+ op: Is,
+ count: 1,
+ },
+ {
+ testString: "PrimaryEmail1@test.invalid",
+ testAttribute: From,
+ op: Isnt,
+ count: 0,
+ },
+ {
+ testString: "PrimaryEmail",
+ testAttribute: From,
+ op: BeginsWith,
+ count: 1,
+ },
+ {
+ testString: "invalid",
+ testAttribute: From,
+ op: BeginsWith,
+ count: 0,
+ },
+ {
+ testString: "invalid",
+ testAttribute: From,
+ op: EndsWith,
+ count: 1,
+ },
+ {
+ testString: "Primary",
+ testAttribute: From,
+ op: EndsWith,
+ count: 0,
+ },
+ {
+ testString: "QAContact",
+ testAttribute: OtherHeader,
+ op: BeginsWith,
+ count: 1,
+ },
+ {
+ testString: "filters",
+ testAttribute: OtherHeader,
+ op: BeginsWith,
+ count: 0,
+ },
+ {
+ testString: "mail.bugs",
+ testAttribute: OtherHeader,
+ op: EndsWith,
+ count: 1,
+ },
+ {
+ testString: "QAContact",
+ testAttribute: OtherHeader,
+ op: EndsWith,
+ count: 0,
+ },
+ {
+ testString: "QAcontact filters@mail.bugs",
+ testAttribute: OtherHeader,
+ op: Is,
+ count: 1,
+ },
+ {
+ testString: "filters@mail.bugs",
+ testAttribute: OtherHeader,
+ op: Is,
+ count: 0,
+ },
+ {
+ testString: "QAcontact filters@mail.bugs",
+ testAttribute: OtherHeader,
+ op: Isnt,
+ count: 0,
+ },
+ {
+ testString: "QAcontact",
+ testAttribute: OtherHeader,
+ op: Isnt,
+ count: 1,
+ },
+ {
+ testString: "filters",
+ testAttribute: OtherHeader,
+ op: Contains,
+ count: 1,
+ },
+ {
+ testString: "foobar",
+ testAttribute: OtherHeader,
+ op: Contains,
+ count: 0,
+ },
+ // test header with multiple occurrences
+ {
+ testString: "one value",
+ testAttribute: OtherHeader,
+ op: Is,
+ customHeader: "X-Duplicated-Header",
+ count: 1,
+ },
+ {
+ testString: "second",
+ testAttribute: OtherHeader,
+ op: Is,
+ customHeader: "X-Duplicated-Header",
+ count: 1,
+ },
+ {
+ testString: "third value for test purposes",
+ testAttribute: OtherHeader,
+ op: Is,
+ customHeader: "X-Duplicated-Header",
+ count: 1,
+ },
+ {
+ testString: "multiline value that needs to be handled.",
+ testAttribute: OtherHeader,
+ op: Is,
+ customHeader: "X-Duplicated-Header",
+ count: 1,
+ },
+ {
+ testString: "one value",
+ testAttribute: OtherHeader,
+ op: Isnt,
+ customHeader: "X-Duplicated-Header",
+ count: 0,
+ },
+ {
+ testString: "second",
+ testAttribute: OtherHeader,
+ op: Isnt,
+ customHeader: "X-Duplicated-Header",
+ count: 0,
+ },
+ {
+ testString: "third value for test purposes",
+ testAttribute: OtherHeader,
+ op: Isnt,
+ customHeader: "X-Duplicated-Header",
+ count: 0,
+ },
+ {
+ testString: "multiline value that needs to be handled.",
+ testAttribute: OtherHeader,
+ op: Isnt,
+ customHeader: "X-Duplicated-Header",
+ count: 0,
+ },
+ {
+ testString: "one",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "X-Duplicated-Header",
+ count: 1,
+ },
+ {
+ testString: "second",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "X-Duplicated-Header",
+ count: 1,
+ },
+ {
+ testString: "purposes",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "X-Duplicated-Header",
+ count: 1,
+ },
+ {
+ testString: "value",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "X-Duplicated-Header",
+ count: 1,
+ },
+ {
+ testString: "that needs to be",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "X-Duplicated-Header",
+ count: 1,
+ },
+ {
+ testString: "fifth",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "X-Duplicated-Header",
+ count: 1,
+ },
+ {
+ testString: "is the end my",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "X-Duplicated-Header",
+ count: 1,
+ },
+ {
+ testString: "the end",
+ testAttribute: OtherHeader,
+ op: EndsWith,
+ customHeader: "X-Duplicated-Header",
+ count: 0,
+ },
+ {
+ testString: "handled.",
+ testAttribute: OtherHeader,
+ op: EndsWith,
+ customHeader: "X-Duplicated-Header",
+ count: 1,
+ },
+ {
+ testString: "one value",
+ testAttribute: OtherHeader,
+ op: EndsWith,
+ customHeader: "X-Duplicated-Header",
+ count: 1,
+ },
+ {
+ testString: "third",
+ testAttribute: OtherHeader,
+ op: BeginsWith,
+ customHeader: "X-Duplicated-Header",
+ count: 1,
+ },
+ {
+ testString: "This is",
+ testAttribute: OtherHeader,
+ op: BeginsWith,
+ customHeader: "X-Duplicated-Header",
+ count: 1,
+ },
+
+ {
+ testString: "nothing",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "X-Duplicated-Header",
+ count: 0,
+ },
+ {
+ testString: "nothing",
+ testAttribute: OtherHeader,
+ op: DoesntContain,
+ customHeader: "X-Duplicated-Header",
+ count: 1,
+ },
+ {
+ testString: "this header tests DB string properties",
+ testAttribute: OtherHeader,
+ op: Is,
+ customHeader: "X-Duplicated-Header-DB",
+ count: 1,
+ },
+ {
+ testString: "which can be handled",
+ testAttribute: OtherHeader,
+ op: Is,
+ customHeader: "X-Duplicated-Header-DB",
+ count: 1,
+ },
+ {
+ testString: "differently than X-Duplicated-Header, so better test it",
+ testAttribute: OtherHeader,
+ op: Is,
+ customHeader: "X-Duplicated-Header-DB",
+ count: 1,
+ },
+ {
+ testString: "this header tests DB string properties",
+ testAttribute: OtherHeader,
+ op: Isnt,
+ customHeader: "X-Duplicated-Header-DB",
+ count: 0,
+ },
+ {
+ testString: "which can be handled",
+ testAttribute: OtherHeader,
+ op: Isnt,
+ customHeader: "X-Duplicated-Header-DB",
+ count: 0,
+ },
+ {
+ testString: "differently than X-Duplicated-Header, so better test it",
+ testAttribute: OtherHeader,
+ op: Isnt,
+ customHeader: "X-Duplicated-Header-DB",
+ count: 0,
+ },
+ {
+ testString: "than X-Duplicated-Header,",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "X-Duplicated-Header-DB",
+ count: 1,
+ },
+ {
+ testString: "than X-Duplicated-Header, so",
+ testAttribute: OtherHeader,
+ op: DoesntContain,
+ customHeader: "X-Duplicated-Header-DB",
+ count: 0,
+ },
+ // test accumulation of received header
+ {
+ // only in first received
+ testString: "caspiaco",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "Received",
+ count: 1,
+ },
+ {
+ // only in second
+ testString: "webapp01.sj.mozilla.com",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "received",
+ count: 1,
+ },
+ {
+ // in neither
+ testString: "not there",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "received",
+ count: 0,
+ },
+ {
+ // not on first line of received
+ testString: "m47LtAFJ007547",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "received",
+ count: 1,
+ },
+ // test multiple line arbitrary headers
+ {
+ // in the first line
+ testString: "SpamAssassin 3.2.3",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "X-Spam-Checker-Version",
+ count: 1,
+ },
+ {
+ // in the second line
+ testString: "host29.example.com",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "X-Spam-Checker-Version",
+ count: 1,
+ },
+ {
+ // spans two lines with space
+ testString: "on host29.example.com",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "X-Spam-Checker-Version",
+ count: 1,
+ },
+ // subject spanning several lines
+ {
+ // on the first line
+ testString: "A filter will",
+ testAttribute: Subject,
+ op: Contains,
+ count: 1,
+ },
+ {
+ testString: "I do not exist",
+ testAttribute: Subject,
+ op: Contains,
+ count: 0,
+ },
+ {
+ // on the second line
+ testString: "this message",
+ testAttribute: Subject,
+ op: Contains,
+ count: 1,
+ },
+ {
+ // spanning second and third line
+ testString: "over many",
+ testAttribute: Subject,
+ op: Contains,
+ count: 1,
+ },
+ // tests of custom headers db values
+ {
+ testString: "a one line header",
+ dbHeader: "oneliner",
+ },
+ {
+ testString: "a two line header",
+ dbHeader: "twoliner",
+ },
+ {
+ testString: "a three line header with lotsa space and tabs",
+ dbHeader: "threeliner",
+ },
+ {
+ testString: "I have no space",
+ dbHeader: "nospace",
+ },
+ {
+ testString: "too much space",
+ dbHeader: "withspace",
+ },
+ // tests of custom db headers in a search
+ {
+ testString: "one line",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "oneliner",
+ count: 1,
+ },
+ {
+ testString: "two line header",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "twoliner",
+ count: 1,
+ },
+ {
+ testString: "three line header with lotsa",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "threeliner",
+ count: 1,
+ },
+ {
+ testString: "I have no space",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "nospace",
+ count: 1,
+ },
+ {
+ testString: "too much space",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "withspace",
+ count: 1,
+ },
+ // test for priority
+ {
+ testString: Ci.nsMsgPriority.lowest,
+ testAttribute: Priority,
+ op: IsHigherThan,
+ count: 1,
+ },
+ {
+ testString: Ci.nsMsgPriority.low,
+ testAttribute: Priority,
+ op: Is,
+ count: 1,
+ },
+ {
+ testString: Ci.nsMsgPriority.normal,
+ testAttribute: Priority,
+ op: IsLowerThan,
+ count: 1,
+ },
+ {
+ testString: Ci.nsMsgPriority.lowest,
+ testAttribute: Priority,
+ op: Isnt,
+ count: 1,
+ },
+ {
+ testString: Ci.nsMsgPriority.low,
+ testAttribute: Priority,
+ op: Isnt,
+ count: 0,
+ },
+
+ // tests of Date header
+ // The internal value of date in the search is PRTime (nanoseconds since Epoch).
+ // Date().getTime() returns milliseconds since Epoch.
+ // The dates used here are tailored for the ../../../data/bugmail12 message.
+ {
+ testString: new Date("Wed, 7 May 2008 14:55:10 -0700").getTime() * 1000,
+ testAttribute: SDate,
+ op: Is,
+ count: 1,
+ },
+ {
+ testString: new Date("Thu, 8 May 2008 14:55:10 -0700").getTime() * 1000,
+ testAttribute: SDate,
+ op: IsBefore,
+ count: 1,
+ },
+ {
+ testString: new Date("Tue, 6 May 2008 14:55:10 -0700").getTime() * 1000,
+ testAttribute: SDate,
+ op: IsAfter,
+ count: 1,
+ },
+ {
+ testString: new Date("Tue, 6 May 2008 14:55:10 -0700").getTime() * 1000,
+ testAttribute: SDate,
+ op: Isnt,
+ count: 1,
+ },
+ {
+ // check bug 248808
+ testString: new Date("Wed, 7 May 2008 14:55:10 -0700").getTime() * 1000,
+ testAttribute: SDate,
+ op: IsBefore,
+ count: 0,
+ },
+ {
+ testString: new Date("Wed, 7 May 2008 14:55:10 -0700").getTime() * 1000,
+ testAttribute: SDate,
+ op: IsAfter,
+ count: 0,
+ },
+];
+
+function run_test() {
+ localAccountUtils.loadLocalMailAccount();
+
+ var copyListener = {
+ OnStartCopy() {},
+ OnProgress(aProgress, aProgressMax) {},
+ SetMessageKey(aKey) {},
+ SetMessageId(aMessageId) {},
+ OnStopCopy(aStatus) {
+ testSearch();
+ },
+ };
+
+ // set value of headers we want parsed into the db
+ Services.prefs.setCharPref(
+ "mailnews.customDBHeaders",
+ "oneLiner twoLiner threeLiner noSpace withSpace X-Duplicated-Header-DB"
+ );
+ // Get a message into the local filestore. function testSearch() continues
+ // the testing after the copy.
+ var bugmail12 = do_get_file("../../../data/bugmail12");
+ do_test_pending();
+ MailServices.copy.copyFileMessage(
+ bugmail12,
+ localAccountUtils.inboxFolder,
+ null,
+ false,
+ 0,
+ "",
+ copyListener,
+ null
+ );
+}
+
+// process each test from queue, calls itself upon completion of each search
+function testSearch() {
+ var test = Tests.shift();
+ if (test && test.dbHeader) {
+ // test of a custom db header
+ dump("testing dbHeader " + test.dbHeader + "\n");
+ let customValue = mailTestUtils
+ .firstMsgHdr(localAccountUtils.inboxFolder)
+ .getStringProperty(test.dbHeader);
+ Assert.equal(customValue, test.testString);
+ do_timeout(0, testSearch);
+ } else if (test) {
+ dump("testing for string '" + test.testString + "'\n");
+ new TestSearch(
+ localAccountUtils.inboxFolder,
+ test.testString,
+ test.testAttribute,
+ test.op,
+ test.count,
+ testSearch,
+ null,
+ test.customHeader ? test.customHeader : "X-Bugzilla-Watch-Reason"
+ );
+ } else {
+ do_test_finished();
+ }
+}
diff --git a/comm/mailnews/search/test/unit/test_searchAddressInAb.js b/comm/mailnews/search/test/unit/test_searchAddressInAb.js
new file mode 100644
index 0000000000..a93f405047
--- /dev/null
+++ b/comm/mailnews/search/test/unit/test_searchAddressInAb.js
@@ -0,0 +1,337 @@
+/* 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/. */
+
+// Testing of to, cc, toorcc in addressbook search features added in bug 187768
+// Added testing of AllAddresses from bug 310359
+
+/* import-globals-from ../../../test/resources/searchTestUtils.js */
+load("../../../resources/searchTestUtils.js");
+
+// add address book setup
+/* import-globals-from ../../../test/resources/abSetup.js */
+load("../../../resources/abSetup.js");
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var ABUri = kPABData.URI;
+
+var IsntInAB = Ci.nsMsgSearchOp.IsntInAB;
+var IsInAB = Ci.nsMsgSearchOp.IsInAB;
+var IsBefore = Ci.nsMsgSearchOp.IsBefore; // control entry that is not enabled
+var Is = Ci.nsMsgSearchOp.Is;
+var Isnt = Ci.nsMsgSearchOp.Isnt;
+
+var offlineMail = Ci.nsMsgSearchScope.offlineMail;
+var onlineMail = Ci.nsMsgSearchScope.onlineMail;
+var offlineMailFilter = Ci.nsMsgSearchScope.offlineMailFilter;
+var onlineMailFilter = Ci.nsMsgSearchScope.onlineMailFilter;
+var news = Ci.nsMsgSearchScope.news; // control entry that is not enabled
+
+var Sender = Ci.nsMsgSearchAttrib.Sender;
+var To = Ci.nsMsgSearchAttrib.To;
+var CCopy = Ci.nsMsgSearchAttrib.CC;
+var ToOrCC = Ci.nsMsgSearchAttrib.ToOrCC;
+var AllAddresses = Ci.nsMsgSearchAttrib.AllAddresses;
+var Keywords = Ci.nsMsgSearchAttrib.Keywords; // control entry that is not enabled
+
+/*
+ * The address available in the test address book is "PrimaryEmail1@test.invalid"
+ * Test emails may also include the address "invalid@example.com"
+ *
+ *
+ * Map of test email contents: (P is "Prim...", I is "inva.." address, N is none)
+ *
+ *
+ * Email From To CC BCC
+ * 1 P I I N
+ * 2 P P P N
+ * 3 I P I N
+ * 4 I I P N
+ * 5 P I P N
+ * 6 I I,P P,I N
+ * 7 I I I P
+ * 8 I P P N
+ *
+ */
+
+var Tests = [
+ {
+ value: ABUri,
+ attrib: Sender,
+ op: IsInAB,
+ count: 3,
+ },
+ {
+ value: ABUri,
+ attrib: To,
+ op: IsInAB,
+ count: 4,
+ },
+ {
+ value: ABUri,
+ attrib: ToOrCC,
+ op: IsInAB,
+ count: 6,
+ },
+ {
+ value: ABUri,
+ attrib: AllAddresses,
+ op: IsInAB,
+ count: 8,
+ },
+ {
+ value: ABUri,
+ attrib: CCopy,
+ op: IsInAB,
+ count: 5,
+ },
+ {
+ value: ABUri,
+ attrib: Sender,
+ op: IsntInAB,
+ count: 5,
+ },
+ {
+ value: ABUri,
+ attrib: To,
+ op: IsntInAB,
+ count: 5,
+ },
+ {
+ value: ABUri,
+ attrib: ToOrCC,
+ op: IsntInAB,
+ count: 6,
+ },
+ {
+ value: ABUri,
+ attrib: AllAddresses,
+ op: IsntInAB,
+ count: 7,
+ },
+ {
+ value: ABUri,
+ attrib: CCopy,
+ op: IsntInAB,
+ count: 4,
+ },
+ {
+ value: "PrimaryEmail1@test.invalid",
+ attrib: AllAddresses,
+ op: Is,
+ count: 8,
+ },
+ {
+ value: "PrimaryEmail1@test.invalid",
+ attrib: AllAddresses,
+ op: Isnt,
+ count: 0,
+ },
+ {
+ value: "invalid@example.com",
+ attrib: AllAddresses,
+ op: Is,
+ count: 7,
+ },
+ {
+ value: "invalid@example.com",
+ attrib: AllAddresses,
+ op: Isnt,
+ count: 1,
+ },
+ {
+ value: "PrimaryEmail1@test.invalid",
+ attrib: ToOrCC,
+ op: Is,
+ count: 6,
+ },
+ {
+ value: "PrimaryEmail1@test.invalid",
+ attrib: ToOrCC,
+ op: Isnt,
+ count: 2,
+ },
+ {
+ value: "invalid@example.com",
+ attrib: ToOrCC,
+ op: Is,
+ count: 6,
+ },
+ {
+ value: "invalid@example.com",
+ attrib: ToOrCC,
+ op: Isnt,
+ count: 2,
+ },
+];
+
+var Files = [
+ "../../../data/bugmail1",
+ "../../../data/bugmail2",
+ "../../../data/bugmail3",
+ "../../../data/bugmail4",
+ "../../../data/bugmail5",
+ "../../../data/bugmail6",
+ "../../../data/bugmail7",
+ "../../../data/bugmail8",
+];
+
+function run_test() {
+ // Setup local mail accounts.
+ localAccountUtils.loadLocalMailAccount();
+
+ loadABFile(
+ "../../../addrbook/test/unit/data/cardForEmail",
+ kPABData.fileName
+ );
+
+ // test that validity table terms are valid
+
+ // offline mail table
+ testValidityTable(offlineMail, IsInAB, Sender, true);
+ testValidityTable(offlineMail, IsInAB, To, true);
+ testValidityTable(offlineMail, IsInAB, ToOrCC, true);
+ testValidityTable(offlineMail, IsInAB, AllAddresses, true);
+ testValidityTable(offlineMail, IsInAB, CCopy, true);
+ testValidityTable(offlineMail, IsInAB, Keywords, false);
+ testValidityTable(offlineMail, IsntInAB, Sender, true);
+ testValidityTable(offlineMail, IsntInAB, To, true);
+ testValidityTable(offlineMail, IsntInAB, ToOrCC, true);
+ testValidityTable(offlineMail, IsntInAB, AllAddresses, true);
+ testValidityTable(offlineMail, IsntInAB, CCopy, true);
+ testValidityTable(offlineMail, IsntInAB, Keywords, false);
+ testValidityTable(offlineMail, IsBefore, Sender, false);
+ testValidityTable(offlineMail, IsBefore, To, false);
+ testValidityTable(offlineMail, IsBefore, ToOrCC, false);
+ testValidityTable(offlineMail, IsBefore, AllAddresses, false);
+ testValidityTable(offlineMail, IsBefore, CCopy, false);
+ testValidityTable(offlineMail, IsBefore, Keywords, false);
+ testValidityTable(offlineMail, Is, AllAddresses, true);
+ testValidityTable(offlineMail, Isnt, AllAddresses, true);
+
+ // offline mail filter table
+ testValidityTable(offlineMailFilter, IsInAB, Sender, true);
+ testValidityTable(offlineMailFilter, IsInAB, To, true);
+ testValidityTable(offlineMailFilter, IsInAB, ToOrCC, true);
+ testValidityTable(offlineMailFilter, IsInAB, AllAddresses, true);
+ testValidityTable(offlineMailFilter, IsInAB, CCopy, true);
+ testValidityTable(offlineMailFilter, IsInAB, Keywords, false);
+ testValidityTable(offlineMailFilter, IsntInAB, Sender, true);
+ testValidityTable(offlineMailFilter, IsntInAB, To, true);
+ testValidityTable(offlineMailFilter, IsntInAB, AllAddresses, true);
+ testValidityTable(offlineMailFilter, IsntInAB, ToOrCC, true);
+ testValidityTable(offlineMailFilter, IsntInAB, CCopy, true);
+ testValidityTable(offlineMailFilter, IsntInAB, Keywords, false);
+ testValidityTable(offlineMailFilter, IsBefore, Sender, false);
+ testValidityTable(offlineMailFilter, IsBefore, To, false);
+ testValidityTable(offlineMailFilter, IsBefore, ToOrCC, false);
+ testValidityTable(offlineMailFilter, IsBefore, AllAddresses, false);
+ testValidityTable(offlineMailFilter, IsBefore, CCopy, false);
+ testValidityTable(offlineMailFilter, IsBefore, Keywords, false);
+ testValidityTable(offlineMailFilter, Is, AllAddresses, true);
+ testValidityTable(offlineMailFilter, Isnt, AllAddresses, true);
+
+ // online mail
+ testValidityTable(onlineMail, IsInAB, Sender, false);
+ testValidityTable(onlineMail, IsInAB, To, false);
+ testValidityTable(onlineMail, IsInAB, ToOrCC, false);
+ testValidityTable(onlineMail, IsInAB, CCopy, false);
+ testValidityTable(onlineMail, IsInAB, Keywords, false);
+ testValidityTable(onlineMail, IsntInAB, Sender, false);
+ testValidityTable(onlineMail, IsntInAB, To, false);
+ testValidityTable(onlineMail, IsntInAB, ToOrCC, false);
+ testValidityTable(onlineMail, IsntInAB, CCopy, false);
+ testValidityTable(onlineMail, IsntInAB, Keywords, false);
+ testValidityTable(onlineMail, IsBefore, Sender, false);
+ testValidityTable(onlineMail, IsBefore, To, false);
+ testValidityTable(onlineMail, IsBefore, ToOrCC, false);
+ testValidityTable(onlineMail, IsBefore, CCopy, false);
+ testValidityTable(onlineMail, IsBefore, Keywords, false);
+
+ // online mail filter
+ testValidityTable(onlineMailFilter, IsInAB, Sender, true);
+ testValidityTable(onlineMailFilter, IsInAB, To, true);
+ testValidityTable(onlineMailFilter, IsInAB, ToOrCC, true);
+ testValidityTable(onlineMailFilter, IsInAB, CCopy, true);
+ testValidityTable(onlineMailFilter, IsInAB, Keywords, false);
+ testValidityTable(onlineMailFilter, IsntInAB, Sender, true);
+ testValidityTable(onlineMailFilter, IsntInAB, To, true);
+ testValidityTable(onlineMailFilter, IsntInAB, ToOrCC, true);
+ testValidityTable(onlineMailFilter, IsntInAB, CCopy, true);
+ testValidityTable(onlineMailFilter, IsntInAB, Keywords, false);
+ testValidityTable(onlineMailFilter, IsBefore, Sender, false);
+ testValidityTable(onlineMailFilter, IsBefore, To, false);
+ testValidityTable(onlineMailFilter, IsBefore, ToOrCC, false);
+ testValidityTable(onlineMailFilter, IsBefore, CCopy, false);
+ testValidityTable(onlineMailFilter, IsBefore, Keywords, false);
+
+ // news
+ testValidityTable(news, IsInAB, Sender, false);
+ testValidityTable(news, IsInAB, To, false);
+ testValidityTable(news, IsInAB, ToOrCC, false);
+ testValidityTable(news, IsInAB, CCopy, false);
+ testValidityTable(news, IsInAB, Keywords, false);
+ testValidityTable(news, IsntInAB, Sender, false);
+ testValidityTable(news, IsntInAB, To, false);
+ testValidityTable(news, IsntInAB, ToOrCC, false);
+ testValidityTable(news, IsntInAB, CCopy, false);
+ testValidityTable(news, IsntInAB, Keywords, false);
+ testValidityTable(news, IsBefore, Sender, false);
+ testValidityTable(news, IsBefore, To, false);
+ testValidityTable(news, IsBefore, ToOrCC, false);
+ testValidityTable(news, IsBefore, CCopy, false);
+ testValidityTable(news, IsBefore, Keywords, false);
+
+ // Get a message into the local filestore. function testAbSearch() continues the testing after the copy.
+ do_test_pending();
+ copyListener.OnStopCopy(null);
+ return true;
+}
+
+var copyListener = {
+ OnStartCopy() {},
+ OnProgress(aProgress, aProgressMax) {},
+ SetMessageKey(aKey) {},
+ SetMessageId(aMessageId) {},
+ OnStopCopy(aStatus) {
+ var fileName = Files.shift();
+ if (fileName) {
+ var file = do_get_file(fileName);
+ MailServices.copy.copyFileMessage(
+ file,
+ localAccountUtils.inboxFolder,
+ null,
+ false,
+ 0,
+ "",
+ copyListener,
+ null
+ );
+ } else {
+ testAbSearch();
+ }
+ },
+};
+
+// Runs at completion of copy
+
+// process each test from queue, calls itself upon completion of each search
+function testAbSearch() {
+ print("Test AbSearch");
+ var test = Tests.shift();
+ if (test) {
+ new TestSearch(
+ localAccountUtils.inboxFolder,
+ test.value,
+ test.attrib,
+ test.op,
+ test.count,
+ testAbSearch
+ );
+ } else {
+ do_test_finished();
+ }
+}
diff --git a/comm/mailnews/search/test/unit/test_searchBody.js b/comm/mailnews/search/test/unit/test_searchBody.js
new file mode 100644
index 0000000000..c2ec73c115
--- /dev/null
+++ b/comm/mailnews/search/test/unit/test_searchBody.js
@@ -0,0 +1,294 @@
+/* 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 tests various body search criteria.
+ */
+/* import-globals-from ../../../test/resources/searchTestUtils.js */
+load("../../../resources/searchTestUtils.js");
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var Isnt = Ci.nsMsgSearchOp.Isnt;
+var Is = Ci.nsMsgSearchOp.Is;
+var IsEmpty = Ci.nsMsgSearchOp.IsEmpty;
+var IsntEmpty = Ci.nsMsgSearchOp.IsntEmpty;
+var Contains = Ci.nsMsgSearchOp.Contains;
+var DoesntContain = Ci.nsMsgSearchOp.DoesntContain;
+var IsBefore = Ci.nsMsgSearchOp.IsBefore; // control entry not enabled
+
+var offlineMail = Ci.nsMsgSearchScope.offlineMail;
+var onlineMail = Ci.nsMsgSearchScope.onlineMail;
+var offlineMailFilter = Ci.nsMsgSearchScope.offlineMailFilter;
+var news = Ci.nsMsgSearchScope.news; // control entry not enabled
+
+var Body = Ci.nsMsgSearchAttrib.Body;
+
+var Files = [
+ "../../../data/base64-1",
+ "../../../data/basic1",
+ "../../../data/multipart-base64-2",
+ "../../../data/bug132340",
+ "../../../data/bad-charset.eml",
+ "../../../data/HTML-with-split-tag1.eml",
+ "../../../data/HTML-with-split-tag2.eml",
+
+ // Base64 encoded bodies.
+ "../../../data/01-plaintext.eml",
+ "../../../data/02-plaintext+attachment.eml",
+ "../../../data/03-HTML.eml",
+ "../../../data/04-HTML+attachment.eml",
+ "../../../data/05-HTML+embedded-image.eml",
+ "../../../data/06-plaintext+HMTL.eml",
+ "../../../data/07-plaintext+(HTML+embedded-image).eml",
+ "../../../data/08-plaintext+HTML+attachment.eml",
+ "../../../data/09-(HTML+embedded-image)+attachment.eml",
+ "../../../data/10-plaintext+(HTML+embedded-image)+attachment.eml",
+
+ // Bodies with non-ASCII characters in UTF-8 and other charsets.
+ "../../../data/11-plaintext.eml",
+ "../../../data/12-plaintext+attachment.eml", // using ISO-8859-7 (Greek)
+ "../../../data/13-HTML.eml",
+ "../../../data/14-HTML+attachment.eml",
+ "../../../data/15-HTML+embedded-image.eml",
+ "../../../data/16-plaintext+HMTL.eml", // text part is base64 encoded
+ "../../../data/17-plaintext+(HTML+embedded-image).eml", // HTML part is base64 encoded
+ "../../../data/18-plaintext+HTML+attachment.eml",
+ "../../../data/19-(HTML+embedded-image)+attachment.eml",
+ "../../../data/20-plaintext+(HTML+embedded-image)+attachment.eml", // using windows-1252
+
+ // Bodies with non-ASCII characters in UTF-8 and other charsets, all encoded with quoted printable.
+ "../../../data/21-plaintext.eml",
+ "../../../data/22-plaintext+attachment.eml", // using ISO-8859-7 (Greek)
+ "../../../data/23-HTML.eml",
+ "../../../data/24-HTML+attachment.eml",
+ "../../../data/25-HTML+embedded-image.eml",
+ "../../../data/26-plaintext+HMTL.eml", // text part is base64 encoded
+ "../../../data/27-plaintext+(HTML+embedded-image).eml", // HTML part is base64 encoded
+ "../../../data/28-plaintext+HTML+attachment.eml",
+ "../../../data/29-(HTML+embedded-image)+attachment.eml",
+ "../../../data/30-plaintext+(HTML+embedded-image)+attachment.eml", // using windows-1252
+
+ // Messages with message attachments, Content-Type: message/rfc822.
+ "../../../data/multipart-message-1.eml", // plaintext, has "bodyOfAttachedMessagePlain"
+ "../../../data/multipart-message-2.eml", // plaintext, base64, non-ASCII, has "bodyOfAttachedMessagePläin"
+ "../../../data/multipart-message-3.eml", // plaintext+HTML, non-ASCII in plaintext, has "bodyOfAttachedMessagePläin"
+ "../../../data/multipart-message-4.eml", // plaintext+HTML, non-ASCII in HTML, has "bodyOfAttachedMessägeHTML"
+
+ // Message using ISO-2022-JP and CTE: quoted-printable.
+ "../../../data/iso-2022-jp-qp.eml", // plaintext, has 日本 (Japan), we shouldn't find =1B$BF|K.
+
+ // Message using ISO-2022-JP and 7bit, but containing something that looks like quoted-printable.
+ // (bug 314637).
+ "../../../data/iso-2022-jp-not-qp.eml", // plaintext, has 現況 which contains =67.
+];
+var Tests = [
+ /* Translate Base64 messages */
+ // "World!" is contained in three messages, but in bug132340 it's not in a text
+ // part and should not be found.
+ { value: "World!", op: Contains, count: 2 },
+ /* Don't match the base64 text */
+ { value: "DQp", op: Contains, count: 0 },
+ /* Nested multipart/mixed, don't match */
+ { value: "PGh", op: Contains, count: 0 },
+ /* An encoded base-64 text/plain match */
+ { value: "base 64 text", op: Contains, count: 1 },
+
+ // From the message with the bad charset.
+ { value: "Mätterhorn", op: Contains, count: 1 },
+ { value: "Matterhörn", op: Contains, count: 1 },
+
+ // Comprehensive test of various MIME structures, messages 01 to 10.
+ // Messages 01 to 10 contain "huhu" once.
+ { value: "huhu", op: Contains, count: 10 },
+
+ // Messages 06, 07, 08, 10 contain "hihi" in the plaintext part.
+ { value: "hihi", op: Contains, count: 4 },
+
+ // The base64 of embedded images and attachments contains "iVBORw" and we don't
+ // want to find that.
+ { value: "iVBORw", op: Contains, count: 0 },
+
+ // The base64 of attachments contains "wMA005J0z" and we don't want to find that.
+ { value: "wMA005J0z", op: Contains, count: 0 },
+
+ // The base64 of the plaintext and HTML parts contains "U2VhcmNoIGZ"
+ // and we don't want to find that.
+ { value: "U2VhcmNoIGZ", op: Contains, count: 0 },
+
+ // Messages 11 and 13 to 20 contain "hühü" once.
+ { value: "hühü", op: Contains, count: 9 },
+ // Message 12 contains Καλησπέρα (good evening in Greek).
+ { value: "Καλησπέρα", op: Contains, count: 1 },
+
+ // Messages 16, 17, 18, 20 contain "hïhï" in the plaintext part.
+ { value: "hïhï", op: Contains, count: 4 },
+
+ // Messages 21 and 23 to 30 contain "höhö" once.
+ { value: "höhö", op: Contains, count: 9 },
+ // Message 22 contains Καλημέρα (good morning in Greek).
+ { value: "Καλημέρα", op: Contains, count: 1 },
+
+ // Messages 21, 23 and 24 contain "softbreak" broken by a soft line break.
+ { value: "softbreak", op: Contains, count: 3 },
+
+ // Messages 16, 17, 18, 20 contain "hähä" in the plaintext part.
+ { value: "hähä", op: Contains, count: 4 },
+
+ // The four messages with message/rfc822 attachment contain "bodyOfAttachedMessagePlain"
+ // or "bodyOfAttachedMessagePläin" in the plaintext part and "bodyOfAttachedMessageHTML"
+ // or "bodyOfAttachedMessägeHTML" in the HTML part.
+ { value: "bodyOfAttachedMessagePlain", op: Contains, count: 2 },
+ { value: "bodyOfAttachedMessagePläin", op: Contains, count: 2 },
+ { value: "bodyOfAttachedMessageHTML", op: Contains, count: 1 },
+ { value: "bodyOfAttachedMessägeHTML", op: Contains, count: 1 },
+
+ // Test that we don't find anything in HTML tags.
+ { value: "ShouldNotFindThis", op: Contains, count: 0 },
+ { value: "ShouldntFindThisEither", op: Contains, count: 0 },
+ { value: "ShouldntFindHref", op: Contains, count: 0 },
+ { value: "ShouldNotFindAcrossLines", op: Contains, count: 0 },
+ { value: "ShouldFindThisAgain", op: Contains, count: 2 },
+ { value: "ShouldFind AcrossLines", op: Contains, count: 2 },
+
+ // Test for ISO-2022-JP and CTE: quoted-printable, also 7bit looking like quoted-printable.
+ { value: "日本", op: Contains, count: 1 },
+ { value: "=1B$BF|K", op: Contains, count: 0 },
+ { value: "現況", op: Contains, count: 1 },
+];
+
+function fixFile(file) {
+ var fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ fstream.init(file, -1, -1, Ci.nsIFileInputStream.CLOSE_ON_EOF);
+ var sstream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ );
+ sstream.init(fstream);
+
+ var str = sstream.read(4096);
+ if (str.startsWith("From ")) {
+ sstream.close();
+ fstream.close();
+ return file;
+ }
+ var data = "From - Tue Oct 02 00:26:47 2007\r\n";
+ do {
+ data += str;
+ str = sstream.read(4096);
+ } while (str.length > 0);
+
+ sstream.close();
+ fstream.close();
+
+ let targetFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ targetFile.initWithFile(do_get_profile());
+ targetFile.append(file.leafName);
+ let ostream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
+ Ci.nsIFileOutputStream
+ );
+ ostream.init(targetFile, -1, -1, 0);
+ ostream.write(data, data.length);
+ ostream.close();
+ return targetFile;
+}
+
+var copyListener = {
+ OnStartCopy() {},
+ OnProgress(aProgress, aProgressMax) {},
+ SetMessageKey(aKey) {},
+ SetMessageId(aMessageId) {},
+ OnStopCopy(aStatus) {
+ var fileName = Files.shift();
+ if (fileName) {
+ var file = fixFile(do_get_file(fileName));
+ MailServices.copy.copyFileMessage(
+ file,
+ localAccountUtils.inboxFolder,
+ null,
+ false,
+ 0,
+ "",
+ copyListener,
+ null
+ );
+ } else {
+ testBodySearch();
+ }
+ },
+};
+
+function run_test() {
+ localAccountUtils.loadLocalMailAccount();
+
+ // test that validity table terms are valid
+
+ // offline mail table
+ testValidityTable(offlineMail, Contains, Body, true);
+ testValidityTable(offlineMail, DoesntContain, Body, true);
+ testValidityTable(offlineMail, Is, Body, true);
+ testValidityTable(offlineMail, Isnt, Body, true);
+ testValidityTable(offlineMail, IsEmpty, Body, false);
+ testValidityTable(offlineMail, IsntEmpty, Body, false);
+ testValidityTable(offlineMail, IsBefore, Body, false);
+
+ // offline mail filter table
+ testValidityTable(offlineMailFilter, Contains, Body, true);
+ testValidityTable(offlineMailFilter, DoesntContain, Body, true);
+ testValidityTable(offlineMailFilter, Is, Body, true);
+ testValidityTable(offlineMailFilter, Isnt, Body, true);
+ testValidityTable(offlineMailFilter, IsEmpty, Body, false);
+ testValidityTable(offlineMailFilter, IsntEmpty, Body, false);
+ testValidityTable(offlineMailFilter, IsBefore, Body, false);
+
+ // online mail
+ testValidityTable(onlineMail, Contains, Body, true);
+ testValidityTable(onlineMail, DoesntContain, Body, true);
+ testValidityTable(onlineMail, Is, Body, false);
+ testValidityTable(onlineMail, Isnt, Body, false);
+ testValidityTable(onlineMail, IsEmpty, Body, false);
+ testValidityTable(onlineMail, IsntEmpty, Body, false);
+ testValidityTable(onlineMail, IsBefore, Body, false);
+
+ // online mail filter
+ /* testValidityTable(onlineMailFilter, Contains, Body, true);
+ testValidityTable(onlineMailFilter, DoesntContain, Body, true);
+ testValidityTable(onlineMailFilter, Is, Body, false);
+ testValidityTable(onlineMailFilter, Isnt, Body, false);
+ testValidityTable(onlineMailFilter, IsEmpty, Body, false);
+ testValidityTable(onlineMailFilter, IsntEmpty, Body, false);
+ testValidityTable(onlineMailFilter, IsBefore, Body, false);*/
+
+ // News does not support body tests
+ testValidityTable(news, Contains, Body, false);
+ testValidityTable(news, DoesntContain, Body, false);
+ testValidityTable(news, Is, Body, false);
+ testValidityTable(news, Isnt, Body, false);
+ testValidityTable(news, IsEmpty, Body, false);
+ testValidityTable(news, IsntEmpty, Body, false);
+ testValidityTable(news, IsBefore, Body, false);
+
+ do_test_pending();
+ copyListener.OnStopCopy(null);
+}
+
+// process each test from queue, calls itself upon completion of each search
+function testBodySearch() {
+ var test = Tests.shift();
+ if (test) {
+ new TestSearch(
+ localAccountUtils.inboxFolder,
+ test.value,
+ Body,
+ test.op,
+ test.count,
+ testBodySearch
+ );
+ } else {
+ do_test_finished();
+ }
+}
diff --git a/comm/mailnews/search/test/unit/test_searchBoolean.js b/comm/mailnews/search/test/unit/test_searchBoolean.js
new file mode 100644
index 0000000000..132acad4a3
--- /dev/null
+++ b/comm/mailnews/search/test/unit/test_searchBoolean.js
@@ -0,0 +1,239 @@
+/* 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/. */
+
+/*
+ * Demonstrates and tests the use of grouped boolean expressions in search terms
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var gSearchSession = Cc[
+ "@mozilla.org/messenger/searchSession;1"
+].createInstance(Ci.nsIMsgSearchSession);
+
+var gHdr; // the message header for the one mailbox message
+
+var Tests = [
+ {
+ A: false,
+ B: false,
+ C: false,
+ D: false,
+ matches: false,
+ },
+ {
+ A: false,
+ B: false,
+ C: false,
+ D: true,
+ matches: false,
+ },
+ {
+ A: false,
+ B: false,
+ C: true,
+ D: false,
+ matches: false,
+ },
+ {
+ A: false,
+ B: false,
+ C: true,
+ D: true,
+ matches: false,
+ },
+ {
+ A: false,
+ B: true,
+ C: false,
+ D: false,
+ matches: false,
+ },
+ {
+ A: false,
+ B: true,
+ C: false,
+ D: true,
+ matches: true,
+ },
+ {
+ A: false,
+ B: true,
+ C: true,
+ D: false,
+ matches: true,
+ },
+ {
+ A: false,
+ B: true,
+ C: true,
+ D: true,
+ matches: true,
+ },
+ {
+ A: true,
+ B: false,
+ C: false,
+ D: false,
+ matches: false,
+ },
+ {
+ A: true,
+ B: false,
+ C: false,
+ D: true,
+ matches: true,
+ },
+ {
+ A: true,
+ B: false,
+ C: true,
+ D: false,
+ matches: true,
+ },
+ {
+ A: true,
+ B: false,
+ C: true,
+ D: true,
+ matches: true,
+ },
+ {
+ A: true,
+ B: true,
+ C: false,
+ D: false,
+ matches: false,
+ },
+ {
+ A: true,
+ B: true,
+ C: false,
+ D: true,
+ matches: true,
+ },
+ {
+ A: true,
+ B: true,
+ C: true,
+ D: false,
+ matches: true,
+ },
+ {
+ A: true,
+ B: true,
+ C: true,
+ D: true,
+ matches: true,
+ },
+];
+
+var gHitCount = 0;
+var searchListener = {
+ onSearchHit(dbHdr, folder) {
+ gHitCount++;
+ },
+ onSearchDone(status) {
+ testSearch();
+ },
+ onNewSearch() {
+ gHitCount = 0;
+ },
+};
+
+function run_test() {
+ localAccountUtils.loadLocalMailAccount();
+
+ /*
+ * I want to create and test a search term that uses this expression:
+ * (A || B ) && (C || D)
+ *
+ * The logical expressions A, B, C, and D will be represented by header
+ * string properties with values of T (true) or F (false).
+ *
+ */
+
+ // add a search term HdrProperty (some string) Is "T", with grouping
+ function addSearchTerm(aHdrProperty, aBeginGrouping, aEndGrouping, aBoolAnd) {
+ let searchTerm = gSearchSession.createTerm();
+ searchTerm.attrib = Ci.nsMsgSearchAttrib.HdrProperty;
+
+ let value = searchTerm.value;
+ // This is tricky - value.attrib must be set before actual values
+ value.attrib = Ci.nsMsgSearchAttrib.HdrProperty;
+ value.str = "T";
+ searchTerm.value = value;
+
+ searchTerm.op = Ci.nsMsgSearchOp.Is;
+ searchTerm.booleanAnd = aBoolAnd;
+ searchTerm.beginsGrouping = aBeginGrouping;
+ searchTerm.endsGrouping = aEndGrouping;
+ searchTerm.hdrProperty = aHdrProperty;
+ gSearchSession.appendTerm(searchTerm);
+ }
+
+ gSearchSession.addScopeTerm(
+ Ci.nsMsgSearchScope.offlineMail,
+ localAccountUtils.inboxFolder
+ );
+ gSearchSession.registerListener(searchListener);
+ // I tried using capital "A" but something makes it lower case internally, so it failed
+ addSearchTerm("a", true, false, true); // "(A"
+ addSearchTerm("b", false, true, false); // " || B)"
+ addSearchTerm("c", true, false, true); // " && (C"
+ addSearchTerm("d", false, true, false); // " || D)"
+
+ var copyListener = {
+ OnStartCopy() {},
+ OnProgress(aProgress, aProgressMax) {},
+ SetMessageKey(aKey) {
+ gHdr = localAccountUtils.inboxFolder.GetMessageHeader(aKey);
+ },
+ SetMessageId(aMessageId) {},
+ OnStopCopy(aStatus) {
+ testSearch();
+ },
+ };
+
+ // Get a message into the local filestore. function testSearch() continues
+ // the testing after the copy.
+ var bugmail1 = do_get_file("../../../data/bugmail1");
+ do_test_pending();
+ MailServices.copy.copyFileMessage(
+ bugmail1,
+ localAccountUtils.inboxFolder,
+ null,
+ false,
+ 0,
+ "",
+ copyListener,
+ null
+ );
+}
+
+var gTest = null;
+// process each test from queue, calls itself upon completion of each search
+function testSearch() {
+ // tests the previous search
+ if (gTest) {
+ Assert.equal(gHitCount, gTest.matches ? 1 : 0);
+ }
+ gTest = Tests.shift();
+ if (gTest) {
+ gHdr.setStringProperty("a", gTest.A ? "T" : "F");
+ gHdr.setStringProperty("b", gTest.B ? "T" : "F");
+ gHdr.setStringProperty("c", gTest.C ? "T" : "F");
+ gHdr.setStringProperty("d", gTest.D ? "T" : "F");
+ try {
+ gSearchSession.search(null);
+ } catch (e) {
+ dump(e);
+ }
+ } else {
+ gSearchSession.unregisterListener(searchListener);
+ do_test_finished();
+ }
+}
diff --git a/comm/mailnews/search/test/unit/test_searchChaining.js b/comm/mailnews/search/test/unit/test_searchChaining.js
new file mode 100644
index 0000000000..4bdbebab57
--- /dev/null
+++ b/comm/mailnews/search/test/unit/test_searchChaining.js
@@ -0,0 +1,90 @@
+/* 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/. */
+
+// Test of chaining of search scopes in a search session. In particular,
+// Bug 541969 made us not search an imap folder if the search scope before it
+// there was an empty local folder.
+
+// main test
+
+/* import-globals-from ../../../test/resources/MessageGenerator.jsm */
+load("../../../resources/MessageGenerator.jsm");
+
+var { IMAPPump, setupIMAPPump, teardownIMAPPump } = ChromeUtils.import(
+ "resource://testing-common/mailnews/IMAPpump.jsm"
+);
+var { ImapMessage } = ChromeUtils.import(
+ "resource://testing-common/mailnews/Imapd.jsm"
+);
+const { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+async function setupFolder() {
+ // add a single message to the imap inbox.
+ let messages = [];
+ let messageGenerator = new MessageGenerator();
+ messages = messages.concat(messageGenerator.makeMessage());
+ let synthMessage = messages[0];
+
+ let msgURI = Services.io.newURI(
+ "data:text/plain;base64," + btoa(synthMessage.toMessageString())
+ );
+ let message = new ImapMessage(msgURI.spec, IMAPPump.mailbox.uidnext++, []);
+ IMAPPump.mailbox.addMessage(message);
+
+ // update folder to download header.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+}
+
+async function searchTest() {
+ // Get the IMAP inbox...
+ var emptyLocal1 =
+ localAccountUtils.rootFolder.createLocalSubfolder("empty 1");
+
+ let searchSession = Cc[
+ "@mozilla.org/messenger/searchSession;1"
+ ].createInstance(Ci.nsIMsgSearchSession);
+
+ let searchTerm = searchSession.createTerm();
+ searchTerm.matchAll = true;
+ searchSession.appendTerm(searchTerm);
+ searchSession.addScopeTerm(Ci.nsMsgSearchScope.offlineMail, emptyLocal1);
+ searchSession.addScopeTerm(Ci.nsMsgSearchScope.onlineMail, IMAPPump.inbox);
+ let listener = new PromiseTestUtils.PromiseSearchNotify(
+ searchSession,
+ searchListener
+ );
+ searchSession.search(null);
+ await listener.promise;
+
+ // After the search completes, there still seem to be active URLs, so we
+ // have to wait before we are done and clear.
+ await PromiseTestUtils.promiseDelay(1000);
+}
+
+// nsIMsgSearchNotify implementation
+var searchListener = {
+ numTotalMessages: 0,
+ QueryInterface: ChromeUtils.generateQI(["nsIMsgSearchNotify"]),
+ onNewSearch() {
+ this.numTotalMessages = 0;
+ },
+ onSearchHit(dbHdr, folder) {
+ this.numTotalMessages++;
+ },
+ onSearchDone(status) {
+ Assert.equal(this.numTotalMessages, 1);
+ return true;
+ },
+};
+
+var tests = [setupIMAPPump, setupFolder, searchTest, teardownIMAPPump];
+
+function run_test() {
+ tests.forEach(x => add_task(x));
+ run_next_test();
+}
diff --git a/comm/mailnews/search/test/unit/test_searchCustomTerm.js b/comm/mailnews/search/test/unit/test_searchCustomTerm.js
new file mode 100644
index 0000000000..baea69767b
--- /dev/null
+++ b/comm/mailnews/search/test/unit/test_searchCustomTerm.js
@@ -0,0 +1,112 @@
+/* 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/. */
+
+/*
+ * Testing of custom search features.
+ *
+ */
+/* import-globals-from ../../../test/resources/searchTestUtils.js */
+load("../../../resources/searchTestUtils.js");
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var kCustomId = "xpcomtest@mozilla.org#test";
+var gHdr;
+
+var Tests = [
+ {
+ setValue: "iamgood",
+ testValue: "iamnotgood",
+ op: Ci.nsMsgSearchOp.Is,
+ count: 0,
+ },
+ {
+ setValue: "iamgood",
+ testValue: "iamgood",
+ op: Ci.nsMsgSearchOp.Is,
+ count: 1,
+ },
+];
+
+// nsIMsgSearchCustomTerm object
+var customTerm = {
+ id: kCustomId,
+ name: "term name",
+ getEnabled(scope, op) {
+ return (
+ scope == Ci.nsMsgSearchScope.offlineMail && op == Ci.nsMsgSearchOp.Is
+ );
+ },
+ getAvailable(scope, op) {
+ return (
+ scope == Ci.nsMsgSearchScope.offlineMail && op == Ci.nsMsgSearchOp.Is
+ );
+ },
+ getAvailableOperators(scope) {
+ return [Ci.nsMsgSearchOp.Is];
+ },
+ match(msgHdr, searchValue, searchOp) {
+ switch (searchOp) {
+ case Ci.nsMsgSearchOp.Is:
+ if (msgHdr.getStringProperty("theTestProperty") == searchValue) {
+ return true;
+ }
+ }
+ return false;
+ },
+};
+
+function run_test() {
+ localAccountUtils.loadLocalMailAccount();
+ MailServices.filters.addCustomTerm(customTerm);
+
+ var copyListener = {
+ OnStartCopy() {},
+ OnProgress(aProgress, aProgressMax) {},
+ SetMessageKey(aKey) {
+ gHdr = localAccountUtils.inboxFolder.GetMessageHeader(aKey);
+ },
+ SetMessageId(aMessageId) {},
+ OnStopCopy(aStatus) {
+ doTest();
+ },
+ };
+
+ // Get a message into the local filestore.
+ // function testSearch() continues the testing after the copy.
+ let bugmail1 = do_get_file("../../../data/bugmail1");
+ do_test_pending();
+
+ MailServices.copy.copyFileMessage(
+ bugmail1,
+ localAccountUtils.inboxFolder,
+ null,
+ false,
+ 0,
+ "",
+ copyListener,
+ null
+ );
+}
+
+function doTest() {
+ let test = Tests.shift();
+ if (test) {
+ gHdr.setStringProperty("theTestProperty", test.setValue);
+ new TestSearch(
+ localAccountUtils.inboxFolder,
+ test.testValue,
+ Ci.nsMsgSearchAttrib.Custom,
+ test.op,
+ test.count,
+ doTest,
+ kCustomId
+ );
+ } else {
+ gHdr = null;
+ do_test_finished();
+ }
+}
diff --git a/comm/mailnews/search/test/unit/test_searchJunk.js b/comm/mailnews/search/test/unit/test_searchJunk.js
new file mode 100644
index 0000000000..fdffd96165
--- /dev/null
+++ b/comm/mailnews/search/test/unit/test_searchJunk.js
@@ -0,0 +1,322 @@
+/* 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/. */
+
+// Testing of search by junk percent and junk score origin
+
+/* import-globals-from ../../../test/resources/searchTestUtils.js */
+load("../../../resources/searchTestUtils.js");
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var IsGreaterThan = Ci.nsMsgSearchOp.IsGreaterThan;
+var IsLessThan = Ci.nsMsgSearchOp.IsLessThan;
+var Is = Ci.nsMsgSearchOp.Is;
+var Isnt = Ci.nsMsgSearchOp.Isnt;
+var IsEmpty = Ci.nsMsgSearchOp.IsEmpty;
+var IsntEmpty = Ci.nsMsgSearchOp.IsntEmpty;
+
+var offlineMail = Ci.nsMsgSearchScope.offlineMail;
+
+var JunkScoreOrigin = Ci.nsMsgSearchAttrib.JunkScoreOrigin;
+var JunkPercent = Ci.nsMsgSearchAttrib.JunkPercent;
+var JunkStatus = Ci.nsMsgSearchAttrib.JunkStatus;
+
+var fileName = "../../../data/bugmail1";
+
+/*
+ * The search for junkpercent is defined as the effective value,
+ * while the "junkpercent" database term is always the result
+ * from the bayes filter. This is optimized to make views that
+ * rely on junk percent search work with the best value for junk
+ * percent, while allowing junk percent from the bayes filter
+ * to be saved for analysis.
+ *
+ * This means that the search for "junk percent" only uses the
+ * database junkpercent value if junkscoreorigin is "plugin".
+ * Otherwise, it uses junkstatus (if set) or defaults to 0
+ * (not junk) if the message is unclassified.
+ */
+
+var Tests = [
+ {
+ // test empty junk status
+ junkScore: false,
+ testValue: 90,
+ attrib: JunkStatus,
+ op: IsEmpty,
+ count: 1,
+ },
+ {
+ junkScore: false,
+ testValue: 90,
+ attrib: JunkStatus,
+ op: IsntEmpty,
+ count: 0,
+ },
+ {
+ junkScore: "0",
+ junkScoreOrigin: "plugin",
+ junkPercent: "10",
+ testValue: 90,
+ attrib: JunkStatus,
+ op: IsntEmpty,
+ count: 1,
+ },
+ {
+ junkScore: "0",
+ junkScoreOrigin: "plugin",
+ junkPercent: "10",
+ testValue: 90,
+ attrib: JunkStatus,
+ op: IsEmpty,
+ count: 0,
+ },
+ {
+ junkScore: "100",
+ junkScoreOrigin: "plugin",
+ junkPercent: "10",
+ testValue: 90,
+ attrib: JunkStatus,
+ op: IsntEmpty,
+ count: 1,
+ },
+ {
+ junkScore: "100",
+ junkScoreOrigin: "plugin",
+ junkPercent: "10",
+ testValue: 90,
+ attrib: JunkStatus,
+ op: IsEmpty,
+ count: 0,
+ },
+ {
+ // Use junkpercent from database
+ junkScore: "0",
+ junkScoreOrigin: "plugin",
+ junkPercent: "10",
+ testValue: 90,
+ attrib: JunkPercent,
+ op: IsGreaterThan,
+ count: 0,
+ },
+ {
+ junkScore: "0",
+ junkScoreOrigin: "plugin",
+ junkPercent: "10",
+ testValue: 90,
+ attrib: JunkPercent,
+ op: IsLessThan,
+ count: 1,
+ },
+ {
+ junkScore: "0",
+ junkScoreOrigin: "plugin",
+ junkPercent: "10",
+ testValue: 90,
+ attrib: JunkPercent,
+ op: Is,
+ count: 0,
+ },
+ {
+ junkScore: "0",
+ junkScoreOrigin: "plugin",
+ junkPercent: "90",
+ testValue: 10,
+ attrib: JunkPercent,
+ op: IsGreaterThan,
+ count: 1,
+ },
+ {
+ junkScore: "0",
+ junkScoreOrigin: "plugin",
+ junkPercent: "90",
+ testValue: 10,
+ attrib: JunkPercent,
+ op: IsLessThan,
+ count: 0,
+ },
+ {
+ junkScore: "0",
+ junkScoreOrigin: "plugin",
+ junkPercent: "10",
+ testValue: 10,
+ attrib: JunkPercent,
+ op: Is,
+ count: 1,
+ },
+ {
+ // values set by user, use junkscore not junkpercent
+ junkScore: "0",
+ junkScoreOrigin: "user",
+ junkPercent: "90",
+ testValue: 50,
+ attrib: JunkPercent,
+ op: IsGreaterThan,
+ count: 0,
+ },
+ {
+ junkScore: "0",
+ junkScoreOrigin: "user",
+ junkPercent: "90",
+ testValue: 50,
+ attrib: JunkPercent,
+ op: IsLessThan,
+ count: 1,
+ },
+ {
+ junkScore: "0",
+ junkScoreOrigin: "user",
+ junkPercent: "90",
+ testValue: 50,
+ attrib: JunkPercent,
+ op: Is,
+ count: 0,
+ },
+ {
+ junkScore: "100",
+ junkScoreOrigin: "user",
+ junkPercent: "10",
+ testValue: 50,
+ attrib: JunkPercent,
+ op: IsGreaterThan,
+ count: 1,
+ },
+ {
+ junkScore: "100",
+ junkScoreOrigin: "user",
+ junkPercent: "10",
+ testValue: 50,
+ attrib: JunkPercent,
+ op: IsLessThan,
+ count: 0,
+ },
+ {
+ junkScore: "0",
+ junkScoreOrigin: "user",
+ junkPercent: "90",
+ testValue: 0,
+ attrib: JunkPercent,
+ op: Is,
+ count: 1,
+ },
+ {
+ // default to 0 when nothing set
+ junkScore: "",
+ junkScoreOrigin: "",
+ junkPercent: "",
+ testValue: 0,
+ attrib: JunkPercent,
+ op: Is,
+ count: 1,
+ },
+ {
+ // junkscoreorigin search tests
+ junkScore: "0",
+ junkScoreOrigin: "plugin",
+ junkPercent: "50",
+ testValue: "plugin",
+ attrib: JunkScoreOrigin,
+ op: Is,
+ count: 1,
+ },
+ {
+ junkScore: "0",
+ junkScoreOrigin: "plugin",
+ junkPercent: "50",
+ testValue: "plugin",
+ attrib: JunkScoreOrigin,
+ op: Isnt,
+ count: 0,
+ },
+ {
+ junkScore: "0",
+ junkScoreOrigin: "filter",
+ junkPercent: "50",
+ testValue: "plugin",
+ attrib: JunkScoreOrigin,
+ op: Is,
+ count: 0,
+ },
+ {
+ junkScore: "0",
+ junkScoreOrigin: "filter",
+ junkPercent: "50",
+ testValue: "plugin",
+ attrib: JunkScoreOrigin,
+ op: Isnt,
+ count: 1,
+ },
+];
+
+function run_test() {
+ localAccountUtils.loadLocalMailAccount();
+
+ // test that validity table terms are valid
+
+ // offline mail table
+ testValidityTable(offlineMail, Is, JunkPercent, true);
+ testValidityTable(offlineMail, Isnt, JunkPercent, false);
+ testValidityTable(offlineMail, IsGreaterThan, JunkPercent, true);
+ testValidityTable(offlineMail, IsLessThan, JunkPercent, true);
+
+ testValidityTable(offlineMail, Is, JunkScoreOrigin, true);
+ testValidityTable(offlineMail, Isnt, JunkScoreOrigin, true);
+ testValidityTable(offlineMail, IsGreaterThan, JunkScoreOrigin, false);
+ testValidityTable(offlineMail, IsLessThan, JunkScoreOrigin, false);
+
+ // Get a message into the local filestore. function testJunkSearch() continues the testing after the copy.
+ do_test_pending();
+ var file = do_get_file(fileName);
+ MailServices.copy.copyFileMessage(
+ file,
+ localAccountUtils.inboxFolder,
+ null,
+ false,
+ 0,
+ "",
+ copyListener,
+ null
+ );
+ return true;
+}
+
+var hdr;
+var copyListener = {
+ OnStartCopy() {},
+ OnProgress(aProgress, aProgressMax) {},
+ SetMessageKey(aKey) {
+ hdr = localAccountUtils.inboxFolder.GetMessageHeader(aKey);
+ },
+ SetMessageId(aMessageId) {},
+ OnStopCopy(aStatus) {
+ testJunkSearch();
+ },
+};
+
+// Runs at completion of each copy
+// process each test from queue, calls itself upon completion of each search
+function testJunkSearch() {
+ var test = Tests.shift();
+ if (test) {
+ if (test.junkScore) {
+ hdr.setStringProperty("junkpercent", test.junkPercent);
+ hdr.setStringProperty("junkscoreorigin", test.junkScoreOrigin);
+ hdr.setStringProperty("junkscore", test.junkScore);
+ }
+
+ new TestSearch(
+ localAccountUtils.inboxFolder,
+ test.testValue,
+ test.attrib,
+ test.op,
+ test.count,
+ testJunkSearch
+ );
+ } else {
+ hdr = null;
+ do_test_finished();
+ }
+}
diff --git a/comm/mailnews/search/test/unit/test_searchLocalizationStrings.js b/comm/mailnews/search/test/unit/test_searchLocalizationStrings.js
new file mode 100644
index 0000000000..9de9b7eb0e
--- /dev/null
+++ b/comm/mailnews/search/test/unit/test_searchLocalizationStrings.js
@@ -0,0 +1,61 @@
+/* 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/. */
+
+// tests that localization strings added in bug 484147 are defined in preferences
+
+var gValidityManager = Cc[
+ "@mozilla.org/mail/search/validityManager;1"
+].getService(Ci.nsIMsgSearchValidityManager);
+
+var gStringBundle = Services.strings.createBundle(
+ "chrome://messenger/locale/search-attributes.properties"
+);
+
+// The following table of valid table scopes matches the allowable table
+// scopes in nsMsgSearchValidityManager::GetTable
+var kValidScopes = [
+ Ci.nsMsgSearchScope.offlineMail,
+ Ci.nsMsgSearchScope.offlineMailFilter,
+ Ci.nsMsgSearchScope.onlineMail,
+ Ci.nsMsgSearchScope.onlineMailFilter,
+ Ci.nsMsgSearchScope.news,
+ Ci.nsMsgSearchScope.newsFilter,
+ Ci.nsMsgSearchScope.localNews,
+ Ci.nsMsgSearchScope.LDAP,
+ Ci.nsMsgSearchScope.LDAPAnd,
+ Ci.nsMsgSearchScope.LocalAB,
+ Ci.nsMsgSearchScope.LocalABAnd,
+];
+
+function run_test() {
+ for (var index = 0; index < kValidScopes.length; ++index) {
+ let scope = kValidScopes[index];
+ let table = gValidityManager.getTable(scope);
+ let attributes = table.getAvailableAttributes();
+ let attribute;
+ while ((attribute = attributes.pop()) && attribute) {
+ let property = gValidityManager.getAttributeProperty(attribute);
+ let valid = false;
+ let localizedString;
+ try {
+ localizedString = gStringBundle.GetStringFromName(property);
+ valid = true;
+ } catch (e) {
+ dump("\n" + e);
+ }
+ valid = valid && localizedString && localizedString.length > 0;
+ if (!valid) {
+ dump(
+ "\nNo valid property for scope = " +
+ scope +
+ " attribute = " +
+ attribute +
+ " property = " +
+ property
+ );
+ }
+ Assert.ok(valid);
+ }
+ }
+}
diff --git a/comm/mailnews/search/test/unit/test_searchTag.js b/comm/mailnews/search/test/unit/test_searchTag.js
new file mode 100644
index 0000000000..321c22a6cf
--- /dev/null
+++ b/comm/mailnews/search/test/unit/test_searchTag.js
@@ -0,0 +1,490 @@
+/* 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/. */
+
+/*
+ * Testing of tag search features.
+ *
+ * Specifically tests changes implemented in bug 217034
+ * Does not do comprehensive testing.
+ *
+ */
+/* import-globals-from ../../../test/resources/searchTestUtils.js */
+load("../../../resources/searchTestUtils.js");
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var Isnt = Ci.nsMsgSearchOp.Isnt;
+var Is = Ci.nsMsgSearchOp.Is;
+var IsEmpty = Ci.nsMsgSearchOp.IsEmpty;
+var IsntEmpty = Ci.nsMsgSearchOp.IsntEmpty;
+var Contains = Ci.nsMsgSearchOp.Contains;
+var DoesntContain = Ci.nsMsgSearchOp.DoesntContain;
+var IsBefore = Ci.nsMsgSearchOp.IsBefore; // control entry not enabled
+
+var offlineMail = Ci.nsMsgSearchScope.offlineMail;
+var onlineMail = Ci.nsMsgSearchScope.onlineMail;
+var offlineMailFilter = Ci.nsMsgSearchScope.offlineMailFilter;
+var onlineMailFilter = Ci.nsMsgSearchScope.onlineMailFilter;
+var news = Ci.nsMsgSearchScope.news; // control entry not enabled
+
+var Keywords = Ci.nsMsgSearchAttrib.Keywords;
+
+// test tags
+var Tag1 = "istag";
+var Tag2 = "notistag";
+var Tag3 = "istagnot";
+var Tag4 = "istagtoo";
+var Tag1Tag4 = Tag1 + " " + Tag4;
+var Tag1Tag3 = Tag1 + " " + Tag3;
+var Tag1Tag1 = Tag1 + " " + Tag1;
+
+var Tests = [
+ // Message has a single valid tag
+ // test the valid tag
+ {
+ msgTag: Tag1,
+ testTag: Tag1,
+ op: Is,
+ count: 1,
+ },
+ {
+ msgTag: Tag1,
+ testTag: Tag1,
+ op: Isnt,
+ count: 0,
+ },
+ {
+ msgTag: Tag1,
+ testTag: Tag1,
+ op: Contains,
+ count: 1,
+ },
+ {
+ msgTag: Tag1,
+ testTag: Tag1,
+ op: DoesntContain,
+ count: 0,
+ },
+ {
+ msgTag: Tag1,
+ testTag: Tag1,
+ op: IsEmpty,
+ count: 0,
+ },
+ {
+ msgTag: Tag1,
+ testTag: Tag1,
+ op: IsntEmpty,
+ count: 1,
+ },
+ // test an invalid tag, should act like empty
+ {
+ msgTag: Tag2,
+ testTag: Tag1,
+ op: Contains,
+ count: 0,
+ },
+ {
+ msgTag: Tag2,
+ testTag: Tag1,
+ op: DoesntContain,
+ count: 1,
+ },
+ {
+ msgTag: Tag2,
+ testTag: Tag1,
+ op: Is,
+ count: 0,
+ },
+ {
+ msgTag: Tag2,
+ testTag: Tag1,
+ op: Isnt,
+ count: 1,
+ },
+ {
+ msgTag: Tag2,
+ testTag: Tag1,
+ op: IsEmpty,
+ count: 1,
+ },
+ {
+ msgTag: Tag2,
+ testTag: Tag1,
+ op: IsntEmpty,
+ count: 0,
+ },
+ // Message has two valid tags
+ // test first tag
+ {
+ msgTag: Tag1Tag4,
+ testTag: Tag1,
+ op: Is,
+ count: 0,
+ },
+ {
+ msgTag: Tag1Tag4,
+ testTag: Tag1,
+ op: Isnt,
+ count: 1,
+ },
+ {
+ msgTag: Tag1Tag4,
+ testTag: Tag1,
+ op: Contains,
+ count: 1,
+ },
+ {
+ msgTag: Tag1Tag4,
+ testTag: Tag1,
+ op: DoesntContain,
+ count: 0,
+ },
+ {
+ msgTag: Tag1Tag4,
+ testTag: Tag1,
+ op: IsEmpty,
+ count: 0,
+ },
+ {
+ msgTag: Tag1Tag4,
+ testTag: Tag1,
+ op: IsntEmpty,
+ count: 1,
+ },
+ // test second tag
+ {
+ msgTag: Tag1Tag4,
+ testTag: Tag4,
+ op: Is,
+ count: 0,
+ },
+ {
+ msgTag: Tag1Tag4,
+ testTag: Tag4,
+ op: Isnt,
+ count: 1,
+ },
+ {
+ msgTag: Tag1Tag4,
+ testTag: Tag4,
+ op: Contains,
+ count: 1,
+ },
+ {
+ msgTag: Tag1Tag4,
+ testTag: Tag4,
+ op: DoesntContain,
+ count: 0,
+ },
+ {
+ msgTag: Tag1Tag4,
+ testTag: Tag4,
+ op: IsEmpty,
+ count: 0,
+ },
+ {
+ msgTag: Tag1Tag4,
+ testTag: Tag4,
+ op: IsntEmpty,
+ count: 1,
+ },
+ // test tag not in message
+ {
+ msgTag: Tag1Tag4,
+ testTag: Tag2,
+ op: Is,
+ count: 0,
+ },
+ {
+ msgTag: Tag1Tag4,
+ testTag: Tag2,
+ op: Isnt,
+ count: 1,
+ },
+ {
+ msgTag: Tag1Tag4,
+ testTag: Tag2,
+ op: Contains,
+ count: 0,
+ },
+ {
+ msgTag: Tag1Tag4,
+ testTag: Tag2,
+ op: DoesntContain,
+ count: 1,
+ },
+ {
+ msgTag: Tag1Tag4,
+ testTag: Tag2,
+ op: IsEmpty,
+ count: 0,
+ },
+ {
+ msgTag: Tag1Tag4,
+ testTag: Tag2,
+ op: IsntEmpty,
+ count: 1,
+ },
+ // empty message
+ {
+ msgTag: "",
+ testTag: Tag2,
+ op: Is,
+ count: 0,
+ },
+ {
+ msgTag: "",
+ testTag: Tag2,
+ op: Isnt,
+ count: 1,
+ },
+ {
+ msgTag: "",
+ testTag: Tag2,
+ op: Contains,
+ count: 0,
+ },
+ {
+ msgTag: "",
+ testTag: Tag2,
+ op: DoesntContain,
+ count: 1,
+ },
+ {
+ msgTag: "",
+ testTag: Tag2,
+ op: IsEmpty,
+ count: 1,
+ },
+ {
+ msgTag: "",
+ testTag: Tag2,
+ op: IsntEmpty,
+ count: 0,
+ },
+ // message with two tags, only one is valid
+ // test with the single valid tag
+ {
+ msgTag: Tag1Tag3,
+ testTag: Tag1,
+ op: Is,
+ count: 1,
+ },
+ {
+ msgTag: Tag1Tag3,
+ testTag: Tag1,
+ op: Isnt,
+ count: 0,
+ },
+ {
+ msgTag: Tag1Tag3,
+ testTag: Tag1,
+ op: Contains,
+ count: 1,
+ },
+ {
+ msgTag: Tag1Tag3,
+ testTag: Tag1,
+ op: DoesntContain,
+ count: 0,
+ },
+ {
+ msgTag: Tag1Tag3,
+ testTag: Tag1,
+ op: IsEmpty,
+ count: 0,
+ },
+ {
+ msgTag: Tag1Tag3,
+ testTag: Tag1,
+ op: IsntEmpty,
+ count: 1,
+ },
+ // test with a tag not in the message
+ {
+ msgTag: Tag1Tag3,
+ testTag: Tag2,
+ op: Is,
+ count: 0,
+ },
+ {
+ msgTag: Tag1Tag3,
+ testTag: Tag2,
+ op: Isnt,
+ count: 1,
+ },
+ {
+ msgTag: Tag1Tag3,
+ testTag: Tag2,
+ op: Contains,
+ count: 0,
+ },
+ {
+ msgTag: Tag1Tag3,
+ testTag: Tag2,
+ op: DoesntContain,
+ count: 1,
+ },
+ {
+ msgTag: Tag1Tag3,
+ testTag: Tag2,
+ op: IsEmpty,
+ count: 0,
+ },
+ {
+ msgTag: Tag1Tag3,
+ testTag: Tag2,
+ op: IsntEmpty,
+ count: 1,
+ },
+ // Message has a duplicated tag
+ // test the tag
+ {
+ msgTag: Tag1Tag1,
+ testTag: Tag1,
+ op: Is,
+ count: 1,
+ },
+ {
+ msgTag: Tag1Tag1,
+ testTag: Tag1,
+ op: Isnt,
+ count: 0,
+ },
+ {
+ msgTag: Tag1Tag1,
+ testTag: Tag1,
+ op: Contains,
+ count: 1,
+ },
+ {
+ msgTag: Tag1Tag1,
+ testTag: Tag1,
+ op: DoesntContain,
+ count: 0,
+ },
+ {
+ msgTag: Tag1Tag1,
+ testTag: Tag1,
+ op: IsEmpty,
+ count: 0,
+ },
+ {
+ msgTag: Tag1Tag1,
+ testTag: Tag1,
+ op: IsntEmpty,
+ count: 1,
+ },
+];
+
+var hdr;
+
+function run_test() {
+ localAccountUtils.loadLocalMailAccount();
+
+ // test that validity table terms are valid
+
+ // offline mail table
+ testValidityTable(offlineMail, Contains, Keywords, true);
+ testValidityTable(offlineMail, DoesntContain, Keywords, true);
+ testValidityTable(offlineMail, Is, Keywords, true);
+ testValidityTable(offlineMail, Isnt, Keywords, true);
+ testValidityTable(offlineMail, IsEmpty, Keywords, true);
+ testValidityTable(offlineMail, IsntEmpty, Keywords, true);
+ testValidityTable(offlineMail, IsBefore, Keywords, false);
+
+ // offline mail filter table
+ testValidityTable(offlineMailFilter, Contains, Keywords, true);
+ testValidityTable(offlineMailFilter, DoesntContain, Keywords, true);
+ testValidityTable(offlineMailFilter, Is, Keywords, true);
+ testValidityTable(offlineMailFilter, Isnt, Keywords, true);
+ testValidityTable(offlineMailFilter, IsEmpty, Keywords, true);
+ testValidityTable(offlineMailFilter, IsntEmpty, Keywords, true);
+ testValidityTable(offlineMailFilter, IsBefore, Keywords, false);
+
+ // online mail
+ testValidityTable(onlineMail, Contains, Keywords, true);
+ testValidityTable(onlineMail, DoesntContain, Keywords, true);
+ testValidityTable(onlineMail, Is, Keywords, false);
+ testValidityTable(onlineMail, Isnt, Keywords, false);
+ testValidityTable(onlineMail, IsEmpty, Keywords, false);
+ testValidityTable(onlineMail, IsntEmpty, Keywords, false);
+ testValidityTable(onlineMail, IsBefore, Keywords, false);
+
+ // online mail filter
+ testValidityTable(onlineMailFilter, Contains, Keywords, true);
+ testValidityTable(onlineMailFilter, DoesntContain, Keywords, true);
+ testValidityTable(onlineMailFilter, Is, Keywords, true);
+ testValidityTable(onlineMailFilter, Isnt, Keywords, true);
+ testValidityTable(onlineMailFilter, IsEmpty, Keywords, true);
+ testValidityTable(onlineMailFilter, IsntEmpty, Keywords, true);
+ testValidityTable(onlineMailFilter, IsBefore, Keywords, false);
+
+ // news
+ testValidityTable(news, Contains, Keywords, false);
+ testValidityTable(news, DoesntContain, Keywords, false);
+ testValidityTable(news, Is, Keywords, false);
+ testValidityTable(news, Isnt, Keywords, false);
+ testValidityTable(news, IsEmpty, Keywords, false);
+ testValidityTable(news, IsntEmpty, Keywords, false);
+ testValidityTable(news, IsBefore, Keywords, false);
+
+ // delete any existing tags
+ let tagArray = MailServices.tags.getAllTags();
+ for (var i = 0; i < tagArray.length; i++) {
+ MailServices.tags.deleteKey(tagArray[i].key);
+ }
+
+ // add as valid tags Tag1 and Tag4
+ MailServices.tags.addTagForKey(Tag1, Tag1, null, null);
+ MailServices.tags.addTagForKey(Tag4, Tag4, null, null);
+
+ var copyListener = {
+ OnStartCopy() {},
+ OnProgress(aProgress, aProgressMax) {},
+ SetMessageKey(aKey) {
+ hdr = localAccountUtils.inboxFolder.GetMessageHeader(aKey);
+ },
+ SetMessageId(aMessageId) {},
+ OnStopCopy(aStatus) {
+ testKeywordSearch();
+ },
+ };
+
+ // Get a message into the local filestore. function testKeywordSearch() continues the testing after the copy.
+ var bugmail1 = do_get_file("../../../data/bugmail1");
+ do_test_pending();
+ MailServices.copy.copyFileMessage(
+ bugmail1,
+ localAccountUtils.inboxFolder,
+ null,
+ false,
+ 0,
+ "",
+ copyListener,
+ null
+ );
+}
+
+// process each test from queue, calls itself upon completion of each search
+function testKeywordSearch() {
+ var test = Tests.shift();
+ if (test) {
+ hdr.setStringProperty("keywords", test.msgTag);
+ new TestSearch(
+ localAccountUtils.inboxFolder,
+ test.testTag,
+ Ci.nsMsgSearchAttrib.Keywords,
+ test.op,
+ test.count,
+ testKeywordSearch
+ );
+ } else {
+ hdr = null;
+ do_test_finished();
+ }
+}
diff --git a/comm/mailnews/search/test/unit/test_searchUint32HdrProperty.js b/comm/mailnews/search/test/unit/test_searchUint32HdrProperty.js
new file mode 100644
index 0000000000..e31f4db7d2
--- /dev/null
+++ b/comm/mailnews/search/test/unit/test_searchUint32HdrProperty.js
@@ -0,0 +1,141 @@
+/* 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/. */
+
+/*
+ * Testing of Uint32HdrProperty search attribute. Adapted from test_search.js
+ */
+
+/* import-globals-from ../../../test/resources/searchTestUtils.js */
+load("../../../resources/searchTestUtils.js");
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var Isnt = Ci.nsMsgSearchOp.Isnt;
+var Is = Ci.nsMsgSearchOp.Is;
+var IsGreaterThan = Ci.nsMsgSearchOp.IsGreaterThan;
+var IsLessThan = Ci.nsMsgSearchOp.IsLessThan;
+
+var Tests = [
+ // test a property that does not exist
+ {
+ hdrProperty: "idonotexist",
+ op: Is,
+ value: 1,
+ count: 0,
+ },
+ {
+ hdrProperty: "idonotexist",
+ op: Isnt,
+ value: 1,
+ count: 1,
+ },
+ // add a property and test its value
+ {
+ setup: function setupProperty() {
+ for (let msgHdr of localAccountUtils.inboxFolder.msgDatabase.enumerateMessages()) {
+ msgHdr.setUint32Property("iam23", 23);
+ }
+ },
+ hdrProperty: "iam23",
+ op: Is,
+ value: 23,
+ count: 1,
+ },
+ {
+ hdrProperty: "iam23",
+ op: Isnt,
+ value: 23,
+ count: 0,
+ },
+ {
+ hdrProperty: "iam23",
+ op: Is,
+ value: 17,
+ count: 0,
+ },
+ {
+ hdrProperty: "iam23",
+ op: Isnt,
+ value: 17,
+ count: 1,
+ },
+ {
+ hdrProperty: "iam23",
+ op: IsGreaterThan,
+ value: 25,
+ count: 0,
+ },
+ {
+ hdrProperty: "iam23",
+ op: IsLessThan,
+ value: 25,
+ count: 1,
+ },
+ {
+ hdrProperty: "iam23",
+ op: IsGreaterThan,
+ value: 17,
+ count: 1,
+ },
+ {
+ hdrProperty: "iam23",
+ op: IsLessThan,
+ value: 17,
+ count: 0,
+ },
+];
+
+function run_test() {
+ localAccountUtils.loadLocalMailAccount();
+
+ var copyListener = {
+ OnStartCopy() {},
+ OnProgress(aProgress, aProgressMax) {},
+ SetMessageKey(aKey) {},
+ SetMessageId(aMessageId) {},
+ OnStopCopy(aStatus) {
+ testSearch();
+ },
+ };
+
+ // Get a message into the local filestore. function testSearch() continues
+ // the testing after the copy.
+ var bugmail1 = do_get_file("../../../data/bugmail1");
+ do_test_pending();
+ MailServices.copy.copyFileMessage(
+ bugmail1,
+ localAccountUtils.inboxFolder,
+ null,
+ false,
+ 0,
+ "",
+ copyListener,
+ null
+ );
+}
+
+// process each test from queue, calls itself upon completion of each search
+function testSearch() {
+ var test = Tests.shift();
+ if (test) {
+ if (test.setup) {
+ test.setup();
+ }
+ new TestSearch(
+ localAccountUtils.inboxFolder,
+ test.value,
+ Ci.nsMsgSearchAttrib.Uint32HdrProperty,
+ test.op,
+ test.count,
+ testSearch,
+ null,
+ null,
+ test.hdrProperty
+ );
+ } else {
+ do_test_finished();
+ }
+}
diff --git a/comm/mailnews/search/test/unit/xpcshell.ini b/comm/mailnews/search/test/unit/xpcshell.ini
new file mode 100644
index 0000000000..3157b90aea
--- /dev/null
+++ b/comm/mailnews/search/test/unit/xpcshell.ini
@@ -0,0 +1,20 @@
+[DEFAULT]
+head = head_mailbase.js
+tail =
+
+[test_base64_decoding.js]
+[test_bug366491.js]
+[test_bug404489.js]
+[test_copyThenMoveManual.js]
+[test_junkWhitelisting.js]
+[test_quarantineFilterMove.js]
+[test_search.js]
+[test_searchAddressInAb.js]
+[test_searchBody.js]
+[test_searchBoolean.js]
+[test_searchChaining.js]
+[test_searchCustomTerm.js]
+[test_searchJunk.js]
+[test_searchLocalizationStrings.js]
+[test_searchTag.js]
+[test_searchUint32HdrProperty.js]