summaryrefslogtreecommitdiffstats
path: root/mailnews/base/search
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews/base/search')
-rw-r--r--mailnews/base/search/content/CustomHeaders.js203
-rw-r--r--mailnews/base/search/content/CustomHeaders.xul57
-rw-r--r--mailnews/base/search/content/FilterEditor.js854
-rw-r--r--mailnews/base/search/content/FilterEditor.xul125
-rw-r--r--mailnews/base/search/content/searchTermOverlay.js536
-rw-r--r--mailnews/base/search/content/searchTermOverlay.xul66
-rw-r--r--mailnews/base/search/content/searchWidgets.xml738
-rw-r--r--mailnews/base/search/content/viewLog.js36
-rw-r--r--mailnews/base/search/content/viewLog.xul49
-rw-r--r--mailnews/base/search/public/moz.build38
-rw-r--r--mailnews/base/search/public/nsIMsgFilter.idl142
-rw-r--r--mailnews/base/search/public/nsIMsgFilterCustomAction.idl90
-rw-r--r--mailnews/base/search/public/nsIMsgFilterHitNotify.idl27
-rw-r--r--mailnews/base/search/public/nsIMsgFilterList.idl108
-rw-r--r--mailnews/base/search/public/nsIMsgFilterPlugin.idl350
-rw-r--r--mailnews/base/search/public/nsIMsgFilterService.idl95
-rw-r--r--mailnews/base/search/public/nsIMsgOperationListener.idl17
-rw-r--r--mailnews/base/search/public/nsIMsgSearchAdapter.idl42
-rw-r--r--mailnews/base/search/public/nsIMsgSearchCustomTerm.idl79
-rw-r--r--mailnews/base/search/public/nsIMsgSearchNotify.idl31
-rw-r--r--mailnews/base/search/public/nsIMsgSearchScopeTerm.idl20
-rw-r--r--mailnews/base/search/public/nsIMsgSearchSession.idl147
-rw-r--r--mailnews/base/search/public/nsIMsgSearchTerm.idl156
-rw-r--r--mailnews/base/search/public/nsIMsgSearchValidityManager.idl26
-rw-r--r--mailnews/base/search/public/nsIMsgSearchValidityTable.idl50
-rw-r--r--mailnews/base/search/public/nsIMsgSearchValue.idl36
-rw-r--r--mailnews/base/search/public/nsIMsgTraitService.idl174
-rw-r--r--mailnews/base/search/public/nsMsgBodyHandler.h112
-rw-r--r--mailnews/base/search/public/nsMsgFilterCore.idl63
-rw-r--r--mailnews/base/search/public/nsMsgResultElement.h40
-rw-r--r--mailnews/base/search/public/nsMsgSearchAdapter.h218
-rw-r--r--mailnews/base/search/public/nsMsgSearchBoolExpression.h107
-rw-r--r--mailnews/base/search/public/nsMsgSearchCore.idl222
-rw-r--r--mailnews/base/search/public/nsMsgSearchScopeTerm.h45
-rw-r--r--mailnews/base/search/public/nsMsgSearchTerm.h85
-rw-r--r--mailnews/base/search/src/Bogofilter.sfd14
-rw-r--r--mailnews/base/search/src/DSPAM.sfd14
-rw-r--r--mailnews/base/search/src/Habeas.sfd8
-rw-r--r--mailnews/base/search/src/POPFile.sfd14
-rw-r--r--mailnews/base/search/src/SpamAssassin.sfd14
-rw-r--r--mailnews/base/search/src/SpamCatcher.sfd14
-rw-r--r--mailnews/base/search/src/SpamPal.sfd14
-rw-r--r--mailnews/base/search/src/moz.build33
-rw-r--r--mailnews/base/search/src/nsMsgBodyHandler.cpp487
-rw-r--r--mailnews/base/search/src/nsMsgFilter.cpp1057
-rw-r--r--mailnews/base/search/src/nsMsgFilter.h103
-rw-r--r--mailnews/base/search/src/nsMsgFilterList.cpp1198
-rw-r--r--mailnews/base/search/src/nsMsgFilterList.h75
-rw-r--r--mailnews/base/search/src/nsMsgFilterService.cpp1216
-rw-r--r--mailnews/base/search/src/nsMsgFilterService.h46
-rw-r--r--mailnews/base/search/src/nsMsgImapSearch.cpp1004
-rw-r--r--mailnews/base/search/src/nsMsgLocalSearch.cpp1022
-rw-r--r--mailnews/base/search/src/nsMsgLocalSearch.h104
-rw-r--r--mailnews/base/search/src/nsMsgSearchAdapter.cpp1332
-rw-r--r--mailnews/base/search/src/nsMsgSearchImap.h37
-rw-r--r--mailnews/base/search/src/nsMsgSearchNews.cpp511
-rw-r--r--mailnews/base/search/src/nsMsgSearchNews.h49
-rw-r--r--mailnews/base/search/src/nsMsgSearchSession.cpp675
-rw-r--r--mailnews/base/search/src/nsMsgSearchSession.h98
-rw-r--r--mailnews/base/search/src/nsMsgSearchTerm.cpp2088
-rw-r--r--mailnews/base/search/src/nsMsgSearchValue.cpp117
-rw-r--r--mailnews/base/search/src/nsMsgSearchValue.h26
-rw-r--r--mailnews/base/search/src/nsMsgTraitService.js239
-rw-r--r--mailnews/base/search/src/nsMsgTraitService.manifest2
64 files changed, 16795 insertions, 0 deletions
diff --git a/mailnews/base/search/content/CustomHeaders.js b/mailnews/base/search/content/CustomHeaders.js
new file mode 100644
index 000000000..174ba100e
--- /dev/null
+++ b/mailnews/base/search/content/CustomHeaders.js
@@ -0,0 +1,203 @@
+/* -*- 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/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var gAddButton;
+var gRemoveButton;
+var gHeaderInputElement;
+var gArrayHdrs;
+var gHdrsList;
+var gContainer;
+var gFilterBundle=null;
+var gCustomBundle=null;
+
+function onLoad()
+{
+ let hdrs = Services.prefs.getCharPref("mailnews.customHeaders");
+ gHeaderInputElement = document.getElementById("headerInput");
+ gHeaderInputElement.focus();
+
+ gHdrsList = document.getElementById("headerList");
+ gArrayHdrs = new Array();
+ 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])
+ gArrayHdrs.splice(i,1); //remove any null elements
+ 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;
+ return true;
+}
+
+function customHeaderOverflow()
+{
+ var nsMsgSearchAttrib = Components.interfaces.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 = GetListItemAttributeStr(listitem);
+ var j=0;
+ for (var i = 0; i < gArrayHdrs.length; i++)
+ {
+ if (gArrayHdrs[i] == selectedHdr)
+ {
+ gArrayHdrs.splice(i,1);
+ break;
+ }
+ }
+}
+
+function GetListItemAttributeStr(listitem)
+{
+ if (listitem)
+ return TrimString(listitem.getAttribute("label"));
+
+ return "";
+}
+
+function addRow(newHdr)
+{
+ var listitem = document.createElement("listitem");
+ listitem.setAttribute("label", newHdr);
+ gHdrsList.appendChild(listitem);
+ return listitem;
+}
+
+function updateAddButton(aDisable)
+{
+ // only update the button if the disabled state changed
+ if (aDisable == gAddButton.disabled)
+ return;
+
+ gAddButton.disabled = aDisable;
+ document.documentElement.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/mailnews/base/search/content/CustomHeaders.xul b/mailnews/base/search/content/CustomHeaders.xul
new file mode 100644
index 000000000..c13a8dba8
--- /dev/null
+++ b/mailnews/base/search/content/CustomHeaders.xul
@@ -0,0 +1,57 @@
+<?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://communicator/skin/" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/CustomHeaders.dtd">
+<dialog id="customHeadersDialog"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="onLoad();"
+ ondialogaccept="return onOk();"
+ ondialogextra1="onAddHeader();"
+ ondialogextra2="onRemoveHeader();"
+ style="width: 30em; height: 25em;"
+ persist="width height screenX screenY"
+ title="&window.title;"
+ buttons="accept,cancel,extra1,extra2">
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_filter" src="chrome://messenger/locale/filter.properties"/>
+ <stringbundle id="bundle_custom" src="chrome://messenger/locale/custom.properties"/>
+ </stringbundleset>
+
+ <script type="application/javascript" src="chrome://messenger/content/CustomHeaders.js"/>
+
+ <grid flex="1">
+ <columns>
+ <column flex="1"/>
+ <column/>
+ </columns>
+ <rows>
+ <row>
+ <label accesskey="&newMsgHeader.accesskey;" control="headerInput" value="&newMsgHeader.label;"/>
+ </row>
+ <row>
+ <textbox id="headerInput" onfocus="this.select();" oninput="onTextInput();"/>
+ </row>
+
+ <row flex="1">
+ <vbox>
+ <listbox id="headerList" flex="1" onselect="updateRemoveButton();" />
+ </vbox>
+
+ <vbox>
+ <button id="addButton"
+ label="&addButton.label;"
+ accesskey="&addButton.accesskey;"
+ dlgtype="extra1"/>
+ <button id="removeButton"
+ label="&removeButton.label;"
+ accesskey="&removeButton.accesskey;"
+ dlgtype="extra2"/>
+ </vbox>
+ </row>
+ </rows>
+ </grid>
+</dialog>
diff --git a/mailnews/base/search/content/FilterEditor.js b/mailnews/base/search/content/FilterEditor.js
new file mode 100644
index 000000000..986185d34
--- /dev/null
+++ b/mailnews/base/search/content/FilterEditor.js
@@ -0,0 +1,854 @@
+/* -*- Mode: Java; 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/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource:///modules/MailUtils.js");
+
+// 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 gSessionFolderListenerAdded = false;
+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;
+// A nsIArray of the currently defined actions in the order they will be run.
+var gActionListOrdered = null;
+
+var gFilterEditorMsgWindow = null;
+
+var nsMsgFilterAction = Components.interfaces.nsMsgFilterAction;
+var nsMsgFilterType = Components.interfaces.nsMsgFilterType;
+var nsIMsgRuleAction = Components.interfaces.nsIMsgRuleAction;
+var nsMsgSearchScope = Components.interfaces.nsMsgSearchScope;
+
+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 = Components.interfaces.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 == Components.interfaces.nsMsgSearchAttrib.Default)
+ term.attrib = Components.interfaces.nsMsgSearchAttrib.Sender;
+
+ term.op = Components.interfaces.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) ==
+ nsMsgSearchScope.newsFilter) ?
+ nsMsgFilterAction.Delete : nsMsgFilterAction.MoveToFolder;
+ gFilter.appendAction(filterAction);
+ initializeDialog(gFilter);
+ }
+ else if ("copiedFilter" in args)
+ {
+ // we are copying a filter
+ var copiedFilter = args.copiedFilter;
+ var 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 i = 0; i < copiedFilter.searchTerms.Count(); i++)
+ {
+ var searchTerm = copiedFilter.searchTerms.QueryElementAt(i,
+ Components.interfaces.nsIMsgSearchTerm);
+
+ var newTerm = newFilter.createTerm();
+ newTerm.attrib = searchTerm.attrib;
+ newTerm.op = searchTerm.op;
+ newTerm.booleanAnd = searchTerm.booleanAnd;
+ newTerm.value = searchTerm.value;
+ newFilter.appendTerm(newTerm);
+ };
+
+ 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(nsMsgFilterType.Incoming | 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 filterEditorOnUnload()
+{
+ if (gSessionFolderListenerAdded)
+ MailServices.mailSession.RemoveFolderListener(gFolderListener);
+}
+
+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.getElementById("FilterEditor").acceptDialog();
+ } else {
+ // If only plain Enter was pressed, add a new rule line.
+ onMore(event);
+ }
+}
+
+function onAccept()
+{
+ try {
+ if (!saveFilter())
+ return false;
+ } catch(e) {Components.utils.reportError(e); return false;}
+
+ // 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;
+ return true;
+}
+
+// the folderListener object
+var gFolderListener = {
+ OnItemAdded: function(parentItem, item) {},
+
+ OnItemRemoved: function(parentItem, item){},
+
+ OnItemPropertyChanged: function(item, property, oldValue, newValue) {},
+
+ OnItemIntPropertyChanged: function(item, property, oldValue, newValue) {},
+
+ OnItemBoolPropertyChanged: function(item, property, oldValue, newValue) {},
+
+ OnItemUnicharPropertyChanged: function(item, property, oldValue, newValue){},
+ OnItemPropertyFlagChanged: function(item, property, oldFlag, newFlag) {},
+
+ OnItemEvent: function(folder, event)
+ {
+ var eventType = event.toString();
+
+ if (eventType == "FolderCreateCompleted")
+ {
+ gActionTargetElement.selectFolder(folder);
+ SetBusyCursor(window, false);
+ }
+ else if (eventType == "FolderCreateFailed")
+ SetBusyCursor(window, false);
+ }
+}
+
+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 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"),
+
+ /**
+ * Returns the currently set filter type (checkboxes) in terms
+ * of a Components.interfaces.nsMsgFilterType value.
+ */
+ getType: function()
+ {
+ let type = nsMsgFilterType.None;
+
+ if (this.checkBoxManual.checked)
+ type |= nsMsgFilterType.Manual;
+
+ if (this.checkBoxIncoming.checked) {
+ if (this.menulistIncoming.selectedItem == this.menuitemAfterPlugins) {
+ type |= nsMsgFilterType.PostPlugin;
+ } else {
+ // this.menuitemBeforePlugins selected
+ if (getScopeFromFilterList(gFilterList) ==
+ nsMsgSearchScope.newsFilter)
+ type |= nsMsgFilterType.NewsRule;
+ else
+ type |= nsMsgFilterType.InboxRule;
+ }
+ }
+
+ if (this.checkBoxArchive.checked)
+ type |= nsMsgFilterType.Archive;
+
+ if (this.checkBoxOutgoing.checked)
+ type |= nsMsgFilterType.PostOutgoing;
+
+ return type;
+ },
+
+ /**
+ * Sets the checkboxes to represent the filter type passed in.
+ *
+ * @param aType the filter type to set in terms
+ * of Components.interfaces.nsMsgFilterType values.
+ */
+ setType: function(aType)
+ {
+ // If there is no type (event) requested, force "when manually run"
+ if (aType == nsMsgFilterType.None)
+ aType = nsMsgFilterType.Manual;
+
+ this.checkBoxManual.checked = aType & nsMsgFilterType.Manual;
+
+ this.checkBoxIncoming.checked = aType & (nsMsgFilterType.PostPlugin |
+ nsMsgFilterType.Incoming);
+
+ this.menulistIncoming.selectedItem = aType & nsMsgFilterType.PostPlugin ?
+ this.menuitemAfterPlugins : this.menuitemBeforePlugins;
+
+ this.checkBoxArchive.checked = aType & nsMsgFilterType.Archive;
+
+ this.checkBoxOutgoing.checked = aType & nsMsgFilterType.PostOutgoing;
+
+ this.updateClassificationMenu();
+ },
+
+ /**
+ * Enable the "before/after classification" menulist depending on
+ * whether "run when incoming mail" is selected.
+ */
+ updateClassificationMenu: function()
+ {
+ this.menulistIncoming.disabled = !this.checkBoxIncoming.checked;
+ updateFilterType();
+ },
+
+ /**
+ * Disable the options unsuitable for deferred accounts.
+ */
+ disableDeferredAccount: function()
+ {
+ this.menuitemAfterPlugins.disabled = true;
+ this.checkBoxOutgoing.disabled = true;
+ }
+ };
+}
+
+function initializeDialog(filter)
+{
+ gFilterNameElement.value = filter.filterName;
+ let filterType = filter.filterType;
+ gFilterTypeSelector.setType(filter.filterType);
+
+ let numActions = filter.actionCount;
+ for (let actionIndex = 0; actionIndex < numActions; actionIndex++)
+ {
+ let filterAction = filter.getActionAt(actionIndex);
+
+ var newActionRow = document.createElement('listitem');
+ newActionRow.setAttribute('initialActionIndex', actionIndex);
+ newActionRow.className = 'ruleaction';
+ gFilterActionList.appendChild(newActionRow);
+ newActionRow.setAttribute('value',
+ filterAction.type == 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())
+ {
+ var newActionRow = document.createElement('listitem');
+ 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 == 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;
+ Components.utils.reportError("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;
+ Components.utils.reportError("Filter not saved because custom search term '" +
+ customTerm.name + "' in rule " + rule_desc(index, obj) + " not available");
+ }
+ }
+ }
+ else
+ {
+ let otherHeader = Components.interfaces.nsMsgSearchAttrib.OtherHeader;
+ let attribValue = (obj.searchattribute.value > otherHeader) ?
+ otherHeader : obj.searchattribute.value;
+ if (!obj.searchattribute
+ .validityTable
+ .getAvailable(attribValue, obj.searchoperator.value))
+ {
+ invalidRule = true;
+ Components.utils.reportError("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;
+ 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.queryElementAt(index, nsIMsgRuleAction)))
+ {
+ // 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 index = 0; index < gFilterActionList.itemCount; index++)
+ gFilterActionList.getItemAtIndex(index).getActionStrings(actionStrings);
+
+ // 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.queryElementAt(i, nsIMsgRuleAction));
+ 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 searchValue = termRoot.searchvalue;
+ let selIndex = searchValue.getAttribute( "selectedIndex" );
+ let children = document.getAnonymousNodes(searchValue);
+ let activeItem = children[selIndex];
+ let attribs = Components.interfaces.nsMsgSearchAttrib;
+
+ // Term, Operator and Value are the three parts of a filter match
+ // Term and Operator are easy to retrieve
+ let term = termRoot.searchattribute.label;
+ let operator = termRoot.searchoperator.label;
+
+ // Values are either popup menu items or edit fields.
+ // For popup menus use activeItem.label; for
+ // edit fields, activeItem.value
+ let value;
+ switch (Number(termRoot.searchattribute.value))
+ {
+ case attribs.Priority:
+ case attribs.MsgStatus:
+ case attribs.Keywords:
+ case attribs.HasAttachmentStatus:
+ case attribs.JunkStatus:
+ case attribs.JunkScoreOrigin:
+ if (activeItem)
+ value = activeItem.label;
+ else
+ value = "";
+ break;
+
+ default:
+ try
+ {
+ value = activeItem.value;
+ }
+ catch (ex)
+ {
+ // We should never get here, but for safety's sake,
+ // let's name the filter "Untitled Filter".
+ stub = gFilterBundle.getString( "untitledFilterName" );
+ // Do not 'Return'. Instead fall through and deal with the untitled filter below.
+ }
+ break;
+ }
+ // We are now ready to name the filter.
+ // If at this point stub is empty, we know that this is not a Match All Filter
+ // and is not an "untitledFilterName" Filter, so assign it a name using
+ // a string format from the Filter Bundle.
+ if (!stub)
+ 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 GetFirstSelectedMsgFolder()
+{
+ var selectedFolder = gActionTargetElement.getAttribute("uri");
+ if (!selectedFolder)
+ return null;
+
+ var msgFolder = MailUtils.getFolderForURI(selectedFolder, true);
+ return msgFolder;
+}
+
+function SearchNewFolderOkCallback(name, uri)
+{
+ var msgFolder = MailUtils.getFolderForURI(uri, true);
+ var imapFolder = null;
+ try
+ {
+ imapFolder = msgFolder.QueryInterface(Components.interfaces.nsIMsgImapMailFolder);
+ }
+ catch(ex) {}
+ if (imapFolder) //imapFolder creation is asynchronous.
+ {
+ if (!gSessionFolderListenerAdded) {
+ try
+ {
+ let notifyFlags = Components.interfaces.nsIFolderListener.event;
+ MailServices.mailSession.AddFolderListener(gFolderListener, notifyFlags);
+ gSessionFolderListenerAdded = true;
+ }
+ catch (ex)
+ {
+ Components.utils.reportError("Error adding to session: " + ex + "\n");
+ }
+ }
+ }
+
+ var msgWindow = GetFilterEditorMsgWindow();
+
+ if (imapFolder)
+ SetBusyCursor(window, true);
+
+ msgFolder.createSubfolder(name, msgWindow);
+
+ if (!imapFolder)
+ {
+ var curFolder = uri+"/"+encodeURIComponent(name);
+ let folder = MailUtils.getFolderForURI(curFolder);
+ gActionTargetElement.selectFolder(folder);
+ }
+}
+
+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 = Components.interfaces.nsIMsgWindow;
+ gFilterEditorMsgWindow = Components.classes[msgWindowContractID].createInstance(nsIMsgWindow);
+ gFilterEditorMsgWindow.domWindow = window;
+ gFilterEditorMsgWindow.rootDocShell.appType = Components.interfaces.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");
+ }
+}
+
+function doHelpButton()
+{
+ openHelp("mail-filters");
+}
+
+function getCustomActions()
+{
+ if (!gCustomActions)
+ {
+ gCustomActions = [];
+ let customActionsEnum = MailServices.filters.getCustomActions();
+ while (customActionsEnum.hasMoreElements())
+ gCustomActions.push(customActionsEnum.getNext().QueryInterface(
+ Components.interfaces.nsIMsgFilterCustomAction));
+ }
+}
+
+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 & nsMsgFilterType.Incoming)
+ return aServerFilterScope;
+
+ // Manual or PostPlugin
+ // local mail allows body and junk types
+ if (aServerFilterScope == nsMsgSearchScope.offlineMailFilter)
+ return nsMsgSearchScope.offlineMail;
+ // IMAP and NEWS online don't allow body
+ return 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.menulist.focus();
+}
diff --git a/mailnews/base/search/content/FilterEditor.xul b/mailnews/base/search/content/FilterEditor.xul
new file mode 100644
index 000000000..0e322264d
--- /dev/null
+++ b/mailnews/base/search/content/FilterEditor.xul
@@ -0,0 +1,125 @@
+<?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/filterDialog.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderPane.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?>
+
+<?xul-overlay href="chrome://messenger/content/searchTermOverlay.xul"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/FilterEditor.dtd">
+
+<dialog id="FilterEditor"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&window.title;"
+ style="&filterEditorDialog.dimensions;"
+ windowtype="mailnews:filtereditor"
+ persist="width height screenX screenY"
+ buttons="accept,cancel"
+ onload="filterEditorOnLoad();"
+ onunload="filterEditorOnUnload();"
+ ondialogaccept="return onAccept();">
+
+ <dummy class="usesMailWidgets"/>
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
+ <stringbundle id="bundle_filter" src="chrome://messenger/locale/filter.properties"/>
+ <stringbundle id="bundle_search" src="chrome://messenger/locale/search.properties"/>
+ </stringbundleset>
+
+ <script type="application/javascript" src="chrome://messenger/content/mailWindowOverlay.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/mailCommands.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/FilterEditor.js"/>
+
+ <commandset>
+ <command id="cmd_updateFilterType" oncommand="updateFilterType();"/>
+ <command id="cmd_updateClassificationMenu" oncommand="gFilterTypeSelector.updateClassificationMenu();"/>
+ </commandset>
+
+ <vbox>
+ <hbox align="center">
+ <label value="&filterName.label;" accesskey="&filterName.accesskey;" control="filterName"/>
+ <textbox flex="1" id="filterName"/>
+ <spacer flex="1"/>
+ </hbox>
+ </vbox>
+
+ <separator class="thin"/>
+
+ <vbox flex="1">
+ <groupbox>
+ <caption label="&contextDesc.label;"/>
+ <grid>
+ <columns>
+ <column/>
+ <column/>
+ </columns>
+ <rows>
+ <row>
+ <checkbox id="runManual"
+ label="&contextManual.label;"
+ accesskey="&contextManual.accesskey;"
+ command="cmd_updateFilterType"/>
+ </row>
+ <row>
+ <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>
+ </row>
+ <row>
+ <checkbox id="runArchive"
+ label="&contextArchive.label;"
+ accesskey="&contextArchive.accesskey;"
+ command="cmd_updateFilterType"/>
+ </row>
+ <row>
+ <checkbox id="runOutgoing"
+ label="&contextOutgoing.label;"
+ accesskey="&contextOutgoing.accesskey;"
+ command="cmd_updateFilterType"/>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <vbox id="searchTermListBox" flex="1"/>
+ </vbox>
+
+ <splitter id="gray_horizontal_splitter" persist="state"/>
+
+ <vbox flex="1">
+ <label value="&filterActionDesc.label;"
+ accesskey="&filterActionDesc.accesskey;"
+ control="filterActionList"/>
+ <listbox id="filterActionList" flex="1" rows="4" minheight="35%"
+ onfocus="setLastActionFocus();" focusedAction="0">
+ <listcols>
+ <listcol flex="&filterActionTypeFlexValue;"/>
+ <listcol flex="&filterActionTargetFlexValue;"/>
+ <listcol class="filler"/>
+ </listcols>
+ </listbox>
+ </vbox>
+
+ <vbox id="statusbar" style="visibility: hidden;">
+ <hbox align="center">
+ <label>
+ &filterActionOrderWarning.label;
+ </label>
+ <label class="text-link" onclick="showActionsOrder();">&filterActionOrder.label;</label>
+ </hbox>
+ </vbox>
+</dialog>
diff --git a/mailnews/base/search/content/searchTermOverlay.js b/mailnews/base/search/content/searchTermOverlay.js
new file mode 100644
index 000000000..faf4ba91c
--- /dev/null
+++ b/mailnews/base/search/content/searchTermOverlay.js
@@ -0,0 +1,536 @@
+/* -*- Mode: Java; 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 gTotalSearchTerms=0;
+var gSearchTermList;
+var gSearchTerms = new Array;
+var gSearchRemovedTerms = new Array;
+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 == Components.interfaces.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;
+ return val;
+ },
+
+ // 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;
+ return val;
+ },
+
+ saveId: function (element, slot) {
+ this[slot] = element.id;
+ },
+
+ getElement: function (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");
+ return val;
+ },
+ get searchoperator() { return this.getElement("internalSearchOperatorId"); },
+ set searchoperator(val) {
+ this.saveId(val, "internalSearchOperatorId");
+ return val;
+ },
+ get searchvalue() { return this.getElement("internalSearchValueId"); },
+ set searchvalue(val) {
+ this.saveId(val, "internalSearchValueId");
+ return val;
+ },
+
+ booleanNodes: null,
+ get booleanAnd() { return this.internalBooleanAnd; },
+ set booleanAnd(val) {
+ this.internalBooleanAnd = val;
+ return val;
+ },
+
+ save: function () {
+ var searchTerm = this.searchTerm;
+ var nsMsgSearchAttrib = Components.interfaces.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: function(searchTerm) {
+ this.internalSearchTerm = searchTerm;
+ this.save();
+ }
+}
+
+var nsIMsgSearchTerm = Components.interfaces.nsIMsgSearchTerm;
+
+function initializeSearchWidgets()
+{
+ gSearchBooleanRadiogroup = document.getElementById("booleanAndGroup");
+ gSearchTermList = document.getElementById("searchTermList");
+
+ // initialize some strings
+ var bundle = document.getElementById('bundle_search');
+ gMoreButtonTooltipText = bundle.getString('moreButtonTooltipText');
+ gLessButtonTooltipText = bundle.getString('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"
+ gSearchBooleanRadiogroup.value = matchAll
+ ? "matchAll"
+ : (booleanAnd ? "and" : "or")
+ var searchTerms = document.getElementById("searchTermList");
+ if (searchTerms)
+ updateSearchTermsListbox(matchAll);
+}
+
+function initializeSearchRows(scope, searchTerms)
+{
+ for (var i = 0; i < searchTerms.Count(); i++) {
+ var searchTerm = searchTerms.QueryElementAt(i, nsIMsgSearchTerm);
+ 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("searchattribute");
+ var searchOperatorList = searchTerms.getElementsByTagName("searchoperator");
+ var searchValueList = searchTerms.getElementsByTagName("searchvalue");
+ for (var 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 (var 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.lastChild.lastChild.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 != "listitem")
+ 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)
+ onMore(null); // fake to get empty row.
+ 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.createElement("searchattribute");
+ var searchOp = document.createElement("searchoperator");
+ var searchVal = document.createElement("searchvalue");
+
+ var moreButton = document.createElement("button");
+ var lessButton = document.createElement("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:scope, searchTerm:searchTerm, initialized:false});
+
+ var editFilter = null;
+ try { editFilter = gFilter; } catch(e) { }
+
+ var editMailView = null;
+ try { editMailView = gMailView; } catch(e) { }
+
+ 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 <listitem> 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 listitem = document.createElement("listitem");
+ listitem.setAttribute("allowevents", "true");
+ for (let i = 0; i < aChildren.length; i++) {
+ let listcell = document.createElement("listcell");
+ 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 == "listitem") break;
+ listitem = listitem.parentNode;
+ }
+
+ if (!listitem) {
+ dump("Error: couldn't find parent listitem!\n");
+ return;
+ }
+
+
+ if (searchTermObj.searchTerm) {
+ gSearchRemovedTerms[gSearchRemovedTerms.length] = 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
+// searchTerms: nsISupportsArray of terms
+// termOwner: object which can contain and create the terms
+// (will be unnecessary if we just make terms creatable
+// via XPCOM)
+function saveSearchTerms(searchTerms, termOwner)
+{
+ var matchAll = gSearchBooleanRadiogroup.value == 'matchAll';
+ var i;
+ for (i = 0; i < gSearchRemovedTerms.length; i++)
+ searchTerms.RemoveElement(gSearchRemovedTerms[i]);
+
+ 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.SetElementAt(i, searchTerm);
+ } catch (ex) {
+ dump("** Error saving element " + i + ": " + ex + "\n");
+ }
+ }
+}
+
+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/mailnews/base/search/content/searchTermOverlay.xul b/mailnews/base/search/content/searchTermOverlay.xul
new file mode 100644
index 000000000..c14e05cb0
--- /dev/null
+++ b/mailnews/base/search/content/searchTermOverlay.xul
@@ -0,0 +1,66 @@
+<?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/. -->
+
+<!DOCTYPE overlay SYSTEM "chrome://messenger/locale/searchTermOverlay.dtd">
+
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript"
+ src="chrome://messenger/content/searchTermOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/dateFormat.js"/>
+
+ <vbox id="searchTermListBox">
+
+ <radiogroup id="booleanAndGroup" orient="horizontal" value="and"
+ oncommand="booleanChanged(event);">
+ <radio value="and" label="&matchAll.label;"
+ accesskey="&matchAll.accesskey;"/>
+ <radio value="or" label="&matchAny.label;"
+ accesskey="&matchAny.accesskey;"/>
+ <radio value="matchAll" id="matchAllItem" label="&matchAllMsgs.label;"
+ accesskey="&matchAllMsgs.accesskey;"/>
+ </radiogroup>
+
+ <hbox flex="1">
+ <hbox id="searchterms"/>
+ <listbox flex="1" id="searchTermList" rows="4" minheight="35%">
+ <listcols>
+ <listcol flex="&searchTermListAttributesFlexValue;"/>
+ <listcol flex="&searchTermListOperatorsFlexValue;"/>
+ <listcol flex="&searchTermListValueFlexValue;"/>
+ <listcol class="filler"/>
+ </listcols>
+
+ <!-- this is what the listitems will look like:
+ <listitem id="searchListItem">
+ <listcell allowevents="true">
+ <searchattribute id="searchAttr1" for="searchOp1,searchValue1" flex="1"/>
+ </listcell>
+ <listcell allowevents="true">
+ <searchoperator id="searchOp1" opfor="searchValue1" flex="1"/>
+ </listcell>
+ <listcell allowevents="true" >
+ <searchvalue id="searchValue1" flex="1"/>
+ </listcell>
+ <listcell>
+ <button label="add"/>
+ <button label="remove"/>
+ </listcell>
+ </listitem>
+ <listitem>
+ <listcell label="the.."/>
+ <listcell label="contains.."/>
+ <listcell label="text here"/>
+ <listcell label="+/-"/>
+ </listitem>
+ -->
+ </listbox>
+
+ </hbox>
+ </vbox>
+
+</overlay>
diff --git a/mailnews/base/search/content/searchWidgets.xml b/mailnews/base/search/content/searchWidgets.xml
new file mode 100644
index 000000000..80ebe38c4
--- /dev/null
+++ b/mailnews/base/search/content/searchWidgets.xml
@@ -0,0 +1,738 @@
+<?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/. -->
+
+<!--
+ This file has the following external dependencies:
+ -gFilterActionStrings from FilterEditor.js
+ -gFilterList from FilterEditor.js
+ -gFilter from FilterEditor.js
+ -gCustomActions from FilterEditor.js
+ -gFilterType from FilterEditor.js
+ -checkActionsReorder from FilterEditor.js
+-->
+
+<!DOCTYPE dialog [
+ <!ENTITY % filterEditorDTD SYSTEM "chrome://messenger/locale/FilterEditor.dtd" >
+%filterEditorDTD;
+ <!ENTITY % messengerDTD SYSTEM "chrome://messenger/locale/messenger.dtd" >
+%messengerDTD;
+]>
+
+<bindings id="filterBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:nc="http://home.netscape.com/NC-rdf#"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="ruleactiontype-menulist">
+ <content>
+ <xul:menulist class="ruleaction-type">
+ <xul:menupopup>
+ <xul:menuitem label="&moveMessage.label;" value="movemessage" enablefornews="false"/>
+ <xul:menuitem label="&copyMessage.label;" value="copymessage"/>
+ <xul:menuseparator enablefornews="false"/>
+ <xul:menuitem label="&forwardTo.label;" value="forwardmessage" enablefornews="false"/>
+ <xul:menuitem label="&replyWithTemplate.label;" value="replytomessage" enablefornews="false"/>
+ <xul:menuseparator/>
+ <xul:menuitem label="&markMessageRead.label;" value="markasread"/>
+ <xul:menuitem label="&markMessageUnread.label;" value="markasunread"/>
+ <xul:menuitem label="&markMessageStarred.label;" value="markasflagged"/>
+ <xul:menuitem label="&setPriority.label;" value="setpriorityto"/>
+ <xul:menuitem label="&addTag.label;" value="addtagtomessage"/>
+ <xul:menuitem label="&setJunkScore.label;" value="setjunkscore" enablefornews="false"/>
+ <xul:menuseparator enableforpop3="true"/>
+ <xul:menuitem label="&deleteMessage.label;" value="deletemessage"/>
+ <xul:menuitem label="&deleteFromPOP.label;" value="deletefrompopserver" enableforpop3="true"/>
+ <xul:menuitem label="&fetchFromPOP.label;" value="fetchfrompopserver" enableforpop3="true"/>
+ <xul:menuseparator/>
+ <xul:menuitem label="&ignoreThread.label;" value="ignorethread"/>
+ <xul:menuitem label="&ignoreSubthread.label;" value="ignoresubthread"/>
+ <xul:menuitem label="&watchThread.label;" value="watchthread"/>
+ <xul:menuseparator/>
+ <xul:menuitem label="&stopExecution.label;" value="stopexecution"/>
+ </xul:menupopup>
+ </xul:menulist>
+ </content>
+
+ <implementation>
+ <constructor>
+ <![CDATA[
+ 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'))
+ {
+ var unavailableActions = this.usedActionsList();
+ // select the first one that's not in the list
+ for (var index = 0; index < this.menuitems.length; index++)
+ {
+ var menu = this.menuitems[index];
+ if (!(menu.value in unavailableActions) && !menu.hidden)
+ {
+ this.menulist.value = menu.value;
+ this.parentNode.setAttribute('value', menu.value);
+ break;
+ }
+ }
+ }
+ else
+ {
+ this.parentNode.mActionTypeInitialized = true;
+ this.parentNode.clearInitialActionIndex();
+ }
+ ]]>
+ </constructor>
+
+ <field name="menulist">document.getAnonymousNodes(this)[0]</field>
+ <field name="menuitems">this.menulist.getElementsByTagNameNS(this.menulist.namespaceURI, 'menuitem')</field>
+
+ <method name="hideInvalidActions">
+ <body>
+ <![CDATA[
+ let menupopup = this.menulist.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, i;
+
+ // disable / enable all elements in the "filteractionlist"
+ // based on the scope and the "enablefornews" attribute
+ elements = menupopup.getElementsByAttribute("enablefornews", "true");
+ for (i = 0; i < elements.length; i++)
+ elements[i].hidden = scope != Components.interfaces.nsMsgSearchScope.newsFilter;
+
+ elements = menupopup.getElementsByAttribute("enablefornews", "false");
+ for (i = 0; i < elements.length; i++)
+ elements[i].hidden = scope == Components.interfaces.nsMsgSearchScope.newsFilter;
+
+ elements = menupopup.getElementsByAttribute("enableforpop3", "true");
+ for (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 (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.getTemplates(false)) {
+ elements = menupopup.getElementsByAttribute("value", "replytomessage");
+ if (elements.length == 1)
+ elements[0].hidden = true;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="addCustomActions">
+ <body>
+ <![CDATA[
+ var menupopup = this.menulist.menupopup;
+ for (var i = 0; i < gCustomActions.length; i++)
+ {
+ var customAction = gCustomActions[i];
+ var menuitem = document.createElementNS(
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "xul:menuitem");
+ menuitem.setAttribute("label", customAction.name);
+ menuitem.setAttribute("value", customAction.id);
+ menuitem.setAttribute("isCustom", "true");
+ menupopup.appendChild(menuitem);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <!-- returns a hash containing all of the filter actions which are currently being used by other filteractionrows -->
+ <method name="usedActionsList">
+ <body>
+ <![CDATA[
+ var usedActions = {};
+ var currentFilterActionRow = this.parentNode;
+ var listBox = currentFilterActionRow.mListBox; // need to account for the list item
+ // now iterate over each list item in the list box
+ for (var index = 0; index < listBox.getRowCount(); index++)
+ {
+ var filterActionRow = listBox.getItemAtIndex(index);
+ if (filterActionRow != currentFilterActionRow)
+ {
+ var actionValue = filterActionRow.getAttribute('value');
+
+ // let custom actions decide if dups are allowed
+ var isCustom = false;
+ for (var 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;
+ // The same with Mark as read/Mark as Unread.
+ else if (actionValue == 'markasread')
+ usedActions['markasunread'] = true;
+ else if (actionValue == 'markasunread')
+ usedActions['markasread'] = true;
+ }
+ }
+ }
+ return usedActions;
+ ]]>
+ </body>
+ </method>
+
+ <!--
+ - Check if there exist any templates in this account.
+ -
+ - @param populateTemplateList If true, create menuitems representing
+ - the found templates.
+ - @param templateMenuList The menulist element to create items in.
+ -
+ - @return True if at least one template was found, otherwise false.
+ -->
+ <method name="getTemplates">
+ <parameter name="populateTemplateList"/>
+ <parameter name="templateMenuList"/>
+ <body>
+ <![CDATA[
+ Components.utils.import("resource:///modules/iteratorUtils.jsm", this);
+ let identitiesRaw = MailServices.accounts
+ .getIdentitiesForServer(gFilterList.folder.server);
+ let identities = Array.from(this.fixIterator(identitiesRaw,
+ Components.interfaces.nsIMsgIdentity));
+
+ if (!identities.length) // typically if this is Local Folders
+ identities.push(MailServices.accounts.defaultAccount.defaultIdentity);
+
+ let templateFound = false;
+ let foldersScanned = [];
+
+ for (let identity of identities) {
+ let enumerator = null;
+ let msgFolder;
+ try {
+ msgFolder = Components.classes["@mozilla.org/rdf/rdf-service;1"]
+ .getService(Components.interfaces.nsIRDFService)
+ .GetResource(identity.stationeryFolder)
+ .QueryInterface(Components.interfaces.nsIMsgFolder);
+ // If we already processed this folder, do not set enumerator
+ // so that we skip this identity.
+ if (foldersScanned.indexOf(msgFolder) == -1) {
+ foldersScanned.push(msgFolder);
+ enumerator = msgFolder.msgDatabase.EnumerateMessages();
+ }
+ } catch (e) {
+ // The Templates folder may not exist, that is OK.
+ }
+
+ if (!enumerator)
+ continue;
+
+ while (enumerator.hasMoreElements()) {
+ let header = enumerator.getNext();
+ if (header instanceof Components.interfaces.nsIMsgDBHdr) {
+ templateFound = true;
+ if (!populateTemplateList)
+ return true;
+ let msgTemplateUri = msgFolder.URI + "?messageId=" +
+ header.messageId + '&subject=' + header.mime2DecodedSubject;
+ let newItem = templateMenuList.appendItem(header.mime2DecodedSubject,
+ msgTemplateUri);
+ }
+ }
+ }
+
+ return templateFound;
+ ]]>
+ </body>
+ </method>
+
+ </implementation>
+
+ <handlers>
+ <handler event="command">
+ <![CDATA[
+ this.parentNode.setAttribute('value', this.menulist.value);
+ checkActionsReorder();
+ ]]>
+ </handler>
+
+ <handler event="popupshowing">
+ <![CDATA[
+ var unavailableActions = this.usedActionsList();
+ for (var index = 0; index < this.menuitems.length; index++)
+ {
+ var menu = this.menuitems[index];
+ menu.setAttribute('disabled', menu.value in unavailableActions);
+ }
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <!-- This binding exists to disable the default binding of a listitem
+ in the search terms. -->
+ <binding id="listitem">
+ <implementation>
+ <method name="_fireEvent">
+ <parameter name="aName"/>
+ <body>
+ <![CDATA[
+ /* This provides a dummy _fireEvent function that
+ the listbox expects to be able to call.
+ See bug 202036. */
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="ruleaction" extends="#listitem">
+ <content allowevents="true">
+ <xul:listcell class="ruleactiontype"
+ orient="vertical" align="stretch" pack="center"/>
+ <xul:listcell class="ruleactiontarget" xbl:inherits="type=value"
+ orient="vertical" align="stretch" pack="center"/>
+ <xul:listcell>
+ <xul:button class="small-button"
+ label="+"
+ tooltiptext="&addAction.tooltip;"
+ oncommand="this.parentNode.parentNode.addRow();"/>
+ <xul:button class="small-button"
+ label="&#x2212;"
+ tooltiptext="&removeAction.tooltip;"
+ oncommand="this.parentNode.parentNode.removeRow();"
+ anonid="removeButton"/>
+ </xul:listcell>
+ </content>
+
+ <implementation>
+ <field name="mListBox">this.parentNode</field>
+ <field name="mRemoveButton">document.getAnonymousElementByAttribute(this, "anonid", "removeButton")</field>
+ <field name="mActionTypeInitialized">false</field>
+ <field name="mRuleActionTargetInitialized">false</field>
+ <field name="mRuleActionType">document.getAnonymousNodes(this)[0]</field>
+
+ <method name="clearInitialActionIndex">
+ <body>
+ <![CDATA[
+ // 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
+
+ if (this.mActionTypeInitialized && this.mRuleActionTargetInitialized)
+ this.removeAttribute('initialActionIndex');
+ ]]>
+ </body>
+ </method>
+
+ <method name="initWithAction">
+ <parameter name="aFilterAction"/>
+ <body>
+ <![CDATA[
+ var filterActionStr;
+ var actionTarget = document.getAnonymousNodes(this)[1];
+ var actionItem = document.getAnonymousNodes(actionTarget);
+ var nsMsgFilterAction = Components.interfaces.nsMsgFilterAction;
+ switch (aFilterAction.type)
+ {
+ case nsMsgFilterAction.Custom:
+ filterActionStr = aFilterAction.customId;
+ if (actionItem)
+ actionItem[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.
+ var needCustomLabel = true;
+ for (var i = 0; i < gCustomActions.length; i++)
+ {
+ if (gCustomActions[i].id == filterActionStr)
+ {
+ needCustomLabel = false;
+ break;
+ }
+ }
+ if (needCustomLabel)
+ {
+ var menuitem = document.createElementNS(
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "xul:menuitem");
+ menuitem.setAttribute("label",
+ gFilterBundle.getString("filterMissingCustomAction"));
+ menuitem.setAttribute("value", filterActionStr);
+ menuitem.disabled = true;
+ this.mRuleActionType.menulist.menupopup.appendChild(menuitem);
+ var scriptError = Components.classes["@mozilla.org/scripterror;1"]
+ .createInstance(Components.interfaces.nsIScriptError);
+ scriptError.init("Missing custom action " + filterActionStr,
+ null, null, 0, 0,
+ Components.interfaces.nsIScriptError.errorFlag,
+ "component javascript");
+ Services.console.logMessage(scriptError);
+ }
+ break;
+ case nsMsgFilterAction.MoveToFolder:
+ case nsMsgFilterAction.CopyToFolder:
+ actionItem[0].value = aFilterAction.targetFolderUri;
+ break;
+ case nsMsgFilterAction.Reply:
+ case nsMsgFilterAction.Forward:
+ actionItem[0].value = aFilterAction.strValue;
+ break;
+ case nsMsgFilterAction.Label:
+ actionItem[0].value = aFilterAction.label;
+ break;
+ case nsMsgFilterAction.ChangePriority:
+ actionItem[0].value = aFilterAction.priority;
+ break;
+ case nsMsgFilterAction.JunkScore:
+ actionItem[0].value = aFilterAction.junkScore;
+ break;
+ case nsMsgFilterAction.AddTag:
+ actionItem[0].value = aFilterAction.strValue;
+ break;
+ default:
+ break;
+ }
+ if (aFilterAction.type != nsMsgFilterAction.Custom)
+ filterActionStr = gFilterActionStrings[aFilterAction.type];
+ document.getAnonymousNodes(this.mRuleActionType)[0]
+ .value = filterActionStr;
+ this.mRuleActionTargetInitialized = true;
+ this.clearInitialActionIndex();
+ checkActionsReorder();
+ ]]>
+ </body>
+ </method>
+
+ <method name="validateAction">
+ <body>
+ <![CDATA[
+ // returns true if this row represents a valid filter action and false otherwise.
+ // This routine also prompts the user.
+ Components.utils.import("resource:///modules/MailUtils.js", this);
+ var filterActionString = this.getAttribute('value');
+ var actionTarget = document.getAnonymousNodes(this)[1];
+ var errorString, customError;
+
+ switch (filterActionString)
+ {
+ case "movemessage":
+ case "copymessage":
+ let msgFolder = document.getAnonymousNodes(actionTarget)[0].value ?
+ this.MailUtils.getFolderForURI(document.getAnonymousNodes(actionTarget)[0].value) : null;
+ if (!msgFolder || !msgFolder.canFileMessages)
+ errorString = "mustSelectFolder";
+ break;
+ case "forwardmessage":
+ if (document.getAnonymousNodes(actionTarget)[0].value.length < 3 ||
+ document.getAnonymousNodes(actionTarget)[0].value.indexOf('@') < 1)
+ errorString = "enterValidEmailAddress";
+ break;
+ case "replytomessage":
+ if (!document.getAnonymousNodes(actionTarget)[0].selectedItem)
+ errorString = "pickTemplateToReplyWith";
+ break;
+ default:
+ // some custom actions have no action value node
+ if (!document.getAnonymousNodes(actionTarget))
+ return true;
+ // locate the correct custom action, and check validity
+ for (var i = 0; i < gCustomActions.length; i++)
+ if (gCustomActions[i].id == filterActionString)
+ {
+ customError =
+ gCustomActions[i].validateActionValue(
+ document.getAnonymousNodes(actionTarget)[0].value,
+ gFilterList.folder, gFilterType);
+ break;
+ }
+ break;
+ }
+
+ errorString = errorString ?
+ gFilterBundle.getString(errorString) :
+ customError;
+ if (errorString)
+ Services.prompt.alert(window, null, errorString);
+
+ return !errorString;
+ ]]>
+ </body>
+ </method>
+
+ <method name="saveToFilter">
+ <parameter name="aFilter"/>
+ <body>
+ <![CDATA[
+ // create a new filter action, fill it in, and then append it to the filter
+ var filterAction = aFilter.createAction();
+ var filterActionString = this.getAttribute('value');
+ filterAction.type = gFilterActionStrings.indexOf(filterActionString);
+ var actionTarget = document.getAnonymousNodes(this)[1];
+ var actionItem = document.getAnonymousNodes(actionTarget);
+ var nsMsgFilterAction = Components.interfaces.nsMsgFilterAction;
+ switch (filterAction.type)
+ {
+ case nsMsgFilterAction.Label:
+ filterAction.label = actionItem[0].getAttribute("value");
+ break;
+ case nsMsgFilterAction.ChangePriority:
+ filterAction.priority = actionItem[0].getAttribute("value");
+ break;
+ case nsMsgFilterAction.MoveToFolder:
+ case nsMsgFilterAction.CopyToFolder:
+ filterAction.targetFolderUri = actionItem[0].value;
+ break;
+ case nsMsgFilterAction.JunkScore:
+ filterAction.junkScore = actionItem[0].value;
+ break;
+ case nsMsgFilterAction.Custom:
+ filterAction.customId = filterActionString;
+ // fall through to set the value
+ default:
+ if (actionItem)
+ filterAction.strValue = actionItem[0].value;
+ break;
+ }
+ aFilter.appendAction(filterAction);
+ ]]>
+ </body>
+ </method>
+
+ <method name="getActionStrings">
+ <parameter name="aActionStrings"/>
+ <body>
+ <![CDATA[
+ // Collect the action names and arguments in a plain string form.
+ let actionTarget = document.getAnonymousNodes(this)[1];
+ let actionItem = document.getAnonymousNodes(actionTarget);
+
+ aActionStrings.push({
+ label: document.getAnonymousNodes(this.mRuleActionType)[0].label,
+ argument: actionItem ?
+ (actionItem[0].label ?
+ actionItem[0].label : actionItem[0].value) : ""
+ });
+ ]]>
+ </body>
+ </method>
+
+ <method name="updateRemoveButton">
+ <body>
+ <![CDATA[
+ // if we only have one row of actions, then disable the remove button for that row
+ this.mListBox.getItemAtIndex(0).mRemoveButton.disabled = this.mListBox.getRowCount() == 1;
+ ]]>
+ </body>
+ </method>
+
+ <method name="addRow">
+ <body>
+ <![CDATA[
+ let listItem = document.createElement('listitem');
+ listItem.className = 'ruleaction';
+ listItem.setAttribute('onfocus','this.storeFocus();');
+ this.mListBox.insertBefore(listItem, this.nextSibling);
+ this.mListBox.ensureElementIsVisible(listItem);
+
+ // make sure the first remove button is enabled
+ this.updateRemoveButton();
+ checkActionsReorder();
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeRow">
+ <body>
+ <![CDATA[
+ // this.mListBox will fail after the row is removed, so save it
+ let listBox = this.mListBox;
+ if (listBox.getRowCount() > 1)
+ this.remove();
+ // can't use 'this' as it is destroyed now
+ listBox.getItemAtIndex(0).updateRemoveButton();
+ checkActionsReorder();
+ ]]>
+ </body>
+ </method>
+
+ <method name="storeFocus">
+ <body>
+ <![CDATA[
+ // When this action row is focused, store its index in the parent listbox.
+ this.mListBox.setAttribute("focusedAction", this.mListBox.getIndexOfItem(this));
+ ]]>
+ </body>
+ </method>
+
+ </implementation>
+ </binding>
+
+ <binding id="ruleactiontarget-base">
+ <implementation>
+ <constructor>
+ <![CDATA[
+ if (this.parentNode.hasAttribute('initialActionIndex'))
+ {
+ let actionIndex = this.parentNode.getAttribute('initialActionIndex');
+ let filterAction = gFilter.getActionAt(actionIndex);
+ this.parentNode.initWithAction(filterAction);
+ }
+ this.parentNode.updateRemoveButton();
+ ]]>
+ </constructor>
+ </implementation>
+ </binding>
+
+ <binding id="ruleactiontarget-tag" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
+ <content>
+ <xul:menulist class="ruleactionitem">
+ <xul:menupopup>
+ </xul:menupopup>
+ </xul:menulist>
+ </content>
+
+ <implementation>
+ <constructor>
+ <![CDATA[
+ let menuPopup = document.getAnonymousNodes(this)[0].menupopup;
+ let tagArray = MailServices.tags.getAllTags({});
+ for (let i = 0; i < tagArray.length; ++i)
+ {
+ var taginfo = tagArray[i];
+ var newMenuItem = document.createElement('menuitem');
+ newMenuItem.setAttribute('label', taginfo.tag);
+ newMenuItem.setAttribute('value', taginfo.key);
+ menuPopup.appendChild(newMenuItem);
+ }
+ // propagating a pre-existing hack to make the tag get displayed correctly in the menulist
+ // now that we've changed the tags for each menu list. We need to use the current selectedIndex
+ // (if its defined) to handle the case where we were initialized with a filter action already.
+ var currentItem = document.getAnonymousNodes(this)[0].selectedItem;
+ document.getAnonymousNodes(this)[0].selectedItem = null;
+ document.getAnonymousNodes(this)[0].selectedItem = currentItem;
+ ]]>
+ </constructor>
+ </implementation>
+ </binding>
+
+ <binding id="ruleactiontarget-priority" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
+ <content>
+ <xul:menulist class="ruleactionitem">
+ <xul:menupopup>
+ <xul:menuitem value="6" label="&highestPriorityCmd.label;"/>
+ <xul:menuitem value="5" label="&highPriorityCmd.label;"/>
+ <xul:menuitem value="4" label="&normalPriorityCmd.label;"/>
+ <xul:menuitem value="3" label="&lowPriorityCmd.label;"/>
+ <xul:menuitem value="2" label="&lowestPriorityCmd.label;"/>
+ </xul:menupopup>
+ </xul:menulist>
+ </content>
+ </binding>
+
+ <binding id="ruleactiontarget-junkscore" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
+ <content>
+ <xul:menulist class="ruleactionitem">
+ <xul:menupopup>
+ <xul:menuitem value="100" label="&junk.label;"/>
+ <xul:menuitem value="0" label="&notJunk.label;"/>
+ </xul:menupopup>
+ </xul:menulist>
+ </content>
+ </binding>
+
+ <binding id="ruleactiontarget-replyto" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
+ <content>
+ <xul:menulist class="ruleactionitem">
+ <xul:menupopup>
+ </xul:menupopup>
+ </xul:menulist>
+ </content>
+
+ <implementation>
+ <constructor>
+ <![CDATA[
+ document.getAnonymousElementByAttribute(
+ this.parentNode, "class", "ruleactiontype")
+ .getTemplates(true, document.getAnonymousNodes(this)[0]);
+ ]]>
+ </constructor>
+ </implementation>
+ </binding>
+
+ <binding id="ruleactiontarget-forwardto" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
+ <content>
+ <xul:textbox class="ruleactionitem" flex="1"/>
+ </content>
+ </binding>
+
+ <binding id="ruleactiontarget-folder" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
+ <content>
+ <xul:menulist class="ruleactionitem folderMenuItem"
+ displayformat="verbose"
+ oncommand="this.parentNode.setPicker(event);">
+ <xul:menupopup type="folder"
+ mode="filing"
+ class="menulist-menupopup"
+ showRecent="true"
+ recentLabel="&recentFolders.label;"
+ showFileHereLabel="true"/>
+ </xul:menulist>
+ </content>
+
+ <implementation>
+ <constructor>
+ <![CDATA[
+ Components.utils.import("resource:///modules/MailUtils.js", this);
+ let folder = this.menulist.value ?
+ this.MailUtils.getFolderForURI(this.menulist.value) :
+ gFilterList.folder;
+ // An account folder is not a move/copy target; show "Choose Folder".
+ folder = folder.isServer ? null : folder;
+ let menupopup = this.menulist.menupopup;
+ // The menupopup constructor needs to finish first.
+ setTimeout(function() { menupopup.selectFolder(folder); }, 0);
+ ]]>
+ </constructor>
+
+ <field name="menulist">document.getAnonymousNodes(this)[0]</field>
+ <method name="setPicker">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ this.menulist.menupopup.selectFolder(aEvent.target._folder);
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+</bindings>
diff --git a/mailnews/base/search/content/viewLog.js b/mailnews/base/search/content/viewLog.js
new file mode 100644
index 000000000..61ae44df7
--- /dev/null
+++ b/mailnews/base/search/content/viewLog.js
@@ -0,0 +1,36 @@
+/* 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 gFilterList;
+var gLogFilters;
+var gLogView;
+
+function onLoad()
+{
+ gFilterList = window.arguments[0].filterList;
+
+ gLogFilters = document.getElementById("logFilters");
+ gLogFilters.checked = gFilterList.loggingEnabled;
+
+ gLogView = document.getElementById("logView");
+
+ // for security, disable JS
+ gLogView.docShell.allowJavascript = false;
+
+ gLogView.setAttribute("src", gFilterList.logURL);
+}
+
+function toggleLogFilters()
+{
+ gFilterList.loggingEnabled = gLogFilters.checked;
+}
+
+function clearLog()
+{
+ gFilterList.clearLog();
+
+ // reload the newly truncated file
+ gLogView.reload();
+}
+
diff --git a/mailnews/base/search/content/viewLog.xul b/mailnews/base/search/content/viewLog.xul
new file mode 100644
index 000000000..4ee766395
--- /dev/null
+++ b/mailnews/base/search/content/viewLog.xul
@@ -0,0 +1,49 @@
+<?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 dialog SYSTEM "chrome://messenger/locale/viewLog.dtd">
+
+<dialog id="viewLogWindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="onLoad();"
+ title="&viewLog.title;"
+ windowtype="mailnews:filterlog"
+ buttons="accept"
+ buttonlabelaccept="&closeLog.label;"
+ buttonaccesskeyaccept="&closeLog.accesskey;"
+ ondialogaccept="window.close();"
+ persist="screenX screenY width height"
+ style="width: 40em; height: 25em;">
+
+ <script type="application/javascript" src="chrome://messenger/content/viewLog.js"/>
+
+ <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>
diff --git a/mailnews/base/search/public/moz.build b/mailnews/base/search/public/moz.build
new file mode 100644
index 000000000..60eb4a18a
--- /dev/null
+++ b/mailnews/base/search/public/moz.build
@@ -0,0 +1,38 @@
+# 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/mailnews/base/search/public/nsIMsgFilter.idl b/mailnews/base/search/public/nsIMsgFilter.idl
new file mode 100644
index 000000000..5571b2f9a
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgFilter.idl
@@ -0,0 +1,142 @@
+/* -*- 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"
+// Disable deprecation warnings generated by nsISupportsArray and associated
+// classes.
+%{C++
+#if defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#elif defined(_MSC_VER)
+#pragma warning (disable : 4996)
+#endif
+%}
+interface nsISupportsArray;
+
+interface nsIArray;
+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 ACString targetFolderUri;
+
+ // target label. throws an exception if the action is not label
+ attribute nsMsgLabelValue label;
+
+ 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 nsISupportsArray searchTerms;
+
+ attribute nsIMsgSearchScopeTerm scope;
+
+ // Marking noscript because "headers" is actually a null-separated
+ // list of headers, which is not scriptable.
+ [noscript] void MatchHdr(in nsIMsgDBHdr msgHdr, in nsIMsgFolder folder,
+ in nsIMsgDatabase db,
+ in string headers,
+ // [array, size_is(headerSize)] in string headers,
+ in unsigned long headerSize, out boolean result);
+
+
+ /*
+ * 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 string 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 nsIArray sortedActionList;
+
+ void SaveToTextFile(in nsIOutputStream aStream);
+};
diff --git a/mailnews/base/search/public/nsIMsgFilterCustomAction.idl b/mailnews/base/search/public/nsIMsgFilterCustomAction.idl
new file mode 100644
index 000000000..d3a1c5584
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgFilterCustomAction.idl
@@ -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/. */
+
+#include "nsMsgFilterCore.idl"
+
+interface nsIArray;
+interface nsIMsgCopyServiceListener;
+interface nsIMsgWindow;
+
+/**
+ * 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 apply 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 apply(in nsIArray msgHdrs /* nsIMsgDBHdr array */,
+ 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/mailnews/base/search/public/nsIMsgFilterHitNotify.idl b/mailnews/base/search/public/nsIMsgFilterHitNotify.idl
new file mode 100644
index 000000000..d85226e60
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgFilterHitNotify.idl
@@ -0,0 +1,27 @@
+/* -*- 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/mailnews/base/search/public/nsIMsgFilterList.idl b/mailnews/base/search/public/nsIMsgFilterList.idl
new file mode 100644
index 000000000..b9c24b526
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgFilterList.idl
@@ -0,0 +1,108 @@
+/* -*- 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;
+
+ 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);
+
+ attribute boolean loggingEnabled;
+
+ 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 intially
+ // (such as the filter list dialog)
+ attribute nsIFile defaultFile;
+ void saveToDefaultFile();
+
+
+ // marking noscript because headers is a null-separated list
+ // of strings, which is not scriptable
+ [noscript]
+ void applyFiltersToHdr(in nsMsgFilterTypeType filterType,
+ in nsIMsgDBHdr msgHdr,
+ in nsIMsgFolder folder,
+ in nsIMsgDatabase db,
+ in string headers,
+ //[array, size_is(headerSize)] in string headers,
+ in unsigned long headerSize,
+ 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 ACString oldUri, in ACString newUri, in boolean caseInsensitive);
+
+ // for filter logging
+ // If both attributes are fetched successfully, they guarantee
+ // the log file exists and is set up with a header.
+ attribute nsIOutputStream logStream;
+ readonly attribute ACString logURL;
+ void clearLog();
+ void flushLogIfNecessary();
+};
+
+
+/* these longs are all actually of type nsMsgFilterMotionValue */
+[scriptable, uuid(d067b528-304e-11d3-a0e1-00a0c900d445)]
+interface nsMsgFilterMotion {
+ const long up = 0;
+ const long down = 1;
+};
diff --git a/mailnews/base/search/public/nsIMsgFilterPlugin.idl b/mailnews/base/search/public/nsIMsgFilterPlugin.idl
new file mode 100644
index 000000000..9632471c2
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgFilterPlugin.idl
@@ -0,0 +1,350 @@
+/* -*- 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 string 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 aTraitCount length of aTraits and aPercents arrays
+ * @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 string aMsgURI,
+ in unsigned long aTraitCount,
+ [array, size_is(aTraitCount)] in unsigned long aTraits,
+ [array, size_is(aTraitCount)] in 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 tokenCount length of arrays that follow
+ * @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 string aMsgUri,
+ in unsigned long aProTrait,
+ in unsigned long tokenCount,
+ [array, size_is(tokenCount)] in wstring tokenStrings,
+ [array, size_is(tokenCount)] in unsigned long tokenPercents,
+ [array, size_is(tokenCount)] in 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 string aMsgURI, in nsIMsgWindow aMsgWindow,
+ in nsIJunkMailClassificationListener aListener);
+
+ void classifyMessages(in unsigned long aCount,
+ [array, size_is(aCount)] in string 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 aTraitCount length of aProTraits, aAntiTraits arrays
+ * @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 string aMsgURI,
+ in unsigned long aTraitCount,
+ [array, size_is(aTraitCount)] in unsigned long aProTraits,
+ [array, size_is(aTraitCount)] in 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 aCount Number of messages to evaluate
+ * @param aMsgURIs array of URIs of the messages to be evaluated
+ * @param aTraitCount length of aProTraits, aAntiTraits arrays
+ * @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 unsigned long aCount,
+ [array, size_is(aCount)] in string aMsgURIs,
+ in unsigned long aTraitCount,
+ [array, size_is(aTraitCount)] in unsigned long aProTraits,
+ [array, size_is(aTraitCount)] in 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 string 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 aOldCount length of aOldTraits array
+ * @param aOldTraits array of trait IDs of the old
+ * message classification(s), if any
+ * @param aNewCount length of aNewTraits array
+ * @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 string aMsgURI,
+ in unsigned long aOldCount,
+ [array, size_is(aOldCount)] in unsigned long aOldTraits,
+ in unsigned long aNewCount,
+ [array, size_is(aNewCount)] in 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 string 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.
+ *
+ * @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 aRemapCount number of items in the parallel arrays aFromTraits,
+ * 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 aFromTraits array of trait ids used in aFile. If aFile contains
+ * trait ids that are not in this array, they are not
+ * remapped, but assummed 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 unsigned long aRemapCount,
+ [optional, array, size_is(aRemapCount)] in unsigned long aFromTraits,
+ [optional, array, size_is(aRemapCount)] in 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/mailnews/base/search/public/nsIMsgFilterService.idl b/mailnews/base/search/public/nsIMsgFilterService.idl
new file mode 100644
index 000000000..8cf69aeed
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgFilterService.idl
@@ -0,0 +1,95 @@
+/* -*- 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 nsISimpleEnumerator;
+interface nsIFile;
+interface nsIMsgFolder;
+interface nsIMsgSearchCustomTerm;
+interface nsIArray;
+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 nsIArray 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 nsIArray 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 enumerator of nsIMsgFilterCustomAction objects
+ */
+ nsISimpleEnumerator 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 enumerator of nsIMsgSearchCustomTerm objects
+ */
+ nsISimpleEnumerator 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);
+
+};
diff --git a/mailnews/base/search/public/nsIMsgOperationListener.idl b/mailnews/base/search/public/nsIMsgOperationListener.idl
new file mode 100644
index 000000000..30751cd5c
--- /dev/null
+++ b/mailnews/base/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/mailnews/base/search/public/nsIMsgSearchAdapter.idl b/mailnews/base/search/public/nsIMsgSearchAdapter.idl
new file mode 100644
index 000000000..b818939a1
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgSearchAdapter.idl
@@ -0,0 +1,42 @@
+/* -*- 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/mailnews/base/search/public/nsIMsgSearchCustomTerm.idl b/mailnews/base/search/public/nsIMsgSearchCustomTerm.idl
new file mode 100644
index 000000000..3983eb1a3
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgSearchCustomTerm.idl
@@ -0,0 +1,79 @@
+/* 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)
+ * @param length object to hold array length
+ *
+ * @return array of operators
+ */
+ void getAvailableOperators(in nsMsgSearchScopeValue scope,
+ out unsigned long length,
+ [retval, array, size_is(length)]
+ out nsMsgSearchOpValue operators);
+
+ /**
+ * 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/mailnews/base/search/public/nsIMsgSearchNotify.idl b/mailnews/base/search/public/nsIMsgSearchNotify.idl
new file mode 100644
index 000000000..c602f708c
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgSearchNotify.idl
@@ -0,0 +1,31 @@
+/* -*- 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/mailnews/base/search/public/nsIMsgSearchScopeTerm.idl b/mailnews/base/search/public/nsIMsgSearchScopeTerm.idl
new file mode 100644
index 000000000..32c590979
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgSearchScopeTerm.idl
@@ -0,0 +1,20 @@
+/* -*- 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/mailnews/base/search/public/nsIMsgSearchSession.idl b/mailnews/base/search/public/nsIMsgSearchSession.idl
new file mode 100644
index 000000000..36ed7409e
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgSearchSession.idl
@@ -0,0 +1,147 @@
+/* -*- 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 nsIMsgHdr;
+interface nsIMsgDatabase;
+// Disable deprecation warnings generated by nsISupportsArray and associated
+// classes.
+%{C++
+#if defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#elif defined(_MSC_VER)
+#pragma warning (disable : 4996)
+#endif
+%}
+interface nsISupportsArray;
+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);
+
+ readonly attribute nsISupportsArray 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 everytime 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();
+
+ [noscript] readonly attribute voidPtr searchParam;
+ readonly attribute nsMsgSearchType searchType;
+
+ [noscript] nsMsgSearchType SetSearchParam(in nsMsgSearchType type,
+ in voidPtr param);
+
+ 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/mailnews/base/search/public/nsIMsgSearchTerm.idl b/mailnews/base/search/public/nsIMsgSearchTerm.idl
new file mode 100644
index 000000000..e464ecd70
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgSearchTerm.idl
@@ -0,0 +1,156 @@
+/* -*- 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 matchLabel(in nsMsgLabelValue aLabelValue);
+ 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.
+ *
+ * N.B. This is noscript because headers is a null-separated list of
+ * strings, which is not scriptable.
+ */
+ [noscript]
+ boolean matchArbitraryHeader(in nsIMsgSearchScopeTerm aScopeTerm,
+ in unsigned long aLength,
+ in string aCharset,
+ in boolean aCharsetOverride,
+ in nsIMsgDBHdr aMsg,
+ in nsIMsgDatabase aDb,
+ //[array, size_is(headerLength)] in string headers,
+ in string aHeaders,
+ in unsigned long aHeaderLength,
+ 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/mailnews/base/search/public/nsIMsgSearchValidityManager.idl b/mailnews/base/search/public/nsIMsgSearchValidityManager.idl
new file mode 100644
index 000000000..dbc7958bd
--- /dev/null
+++ b/mailnews/base/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/mailnews/base/search/public/nsIMsgSearchValidityTable.idl b/mailnews/base/search/public/nsIMsgSearchValidityTable.idl
new file mode 100644
index 000000000..43856c88b
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgSearchValidityTable.idl
@@ -0,0 +1,50 @@
+/* -*- 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"
+// Disable deprecation warnings generated by nsISupportsArray and associated
+// classes.
+%{C++
+#if defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#elif defined(_MSC_VER)
+#pragma warning (disable : 4996)
+#endif
+%}
+interface nsISupportsArray;
+
+[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);
+
+ [noscript] void validateTerms(in nsISupportsArray terms);
+
+ readonly attribute long numAvailAttribs;
+
+ void getAvailableAttributes(out unsigned long length,
+ [retval, array, size_is(length)]
+ out nsMsgSearchAttribValue attrs);
+
+ void getAvailableOperators(in nsMsgSearchAttribValue attrib,
+ out unsigned long length,
+ [retval, array, size_is(length)]
+ out nsMsgSearchOpValue operators);
+
+ void setDefaultAttrib(in nsMsgSearchAttribValue defaultAttrib);
+};
diff --git a/mailnews/base/search/public/nsIMsgSearchValue.idl b/mailnews/base/search/public/nsIMsgSearchValue.idl
new file mode 100644
index 000000000..f6d4845bd
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgSearchValue.idl
@@ -0,0 +1,36 @@
+/* -*- 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 nsMsgLabelValue label;
+ 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/mailnews/base/search/public/nsIMsgTraitService.idl b/mailnews/base/search/public/nsIMsgTraitService.idl
new file mode 100644
index 000000000..78d9704e3
--- /dev/null
+++ b/mailnews/base/search/public/nsIMsgTraitService.idl
@@ -0,0 +1,174 @@
+/* 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 identifer "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 traits to be analyzed by the bayesian code. This is
+ * a pair of traits: a "pro" trait of messages that match the trait (and is
+ * set enabled) and an "anti" trait of messages that do not match the trait.
+ *
+ * @param count length of proIndices and antiIndices arrays
+ * @param proIndices trait internal index for "pro" trait to analyze
+ * @param antiIndices trait internal index for corresponding "anti" traits
+ */
+ void getEnabledIndices(out unsigned long count,
+ [array, size_is(count)] out unsigned long proIndices,
+ [array, size_is(count)] out unsigned long antiIndices);
+
+ /**
+ * 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
+ * @param aLength length of array of aliases
+ * @param aAliases array of internal identifiers for aliases
+ */
+ void getAliases(in unsigned long aTraitIndex, out unsigned long aLength,
+ [retval, array, size_is(aLength)] out unsigned long aAliases);
+
+};
diff --git a/mailnews/base/search/public/nsMsgBodyHandler.h b/mailnews/base/search/public/nsMsgBodyHandler.h
new file mode 100644
index 000000000..417f166d6
--- /dev/null
+++ b/mailnews/base/search/public/nsMsgBodyHandler.h
@@ -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/. */
+
+
+#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);
+
+ // Transformations
+ void SetStripHtml (bool strip) { m_stripHtml = strip; }
+ 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_stripHtml; // true if we're supposed to strip off HTML tags
+ 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_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/mailnews/base/search/public/nsMsgFilterCore.idl b/mailnews/base/search/public/nsMsgFilterCore.idl
new file mode 100644
index 000000000..66ecb076a
--- /dev/null
+++ b/mailnews/base/search/public/nsMsgFilterCore.idl
@@ -0,0 +1,63 @@
+/* -*- 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 {
+ /* 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 All = Incoming | Manual;
+};
+
+typedef long nsMsgFilterMotionValue;
+
+typedef long nsMsgFilterIndex;
+
+typedef long nsMsgRuleActionType;
+
+[scriptable, uuid(7726FE79-AFA3-4a39-8292-733AEE288737)]
+interface nsMsgFilterAction {
+
+ // 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 Label=8;
+ 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/mailnews/base/search/public/nsMsgResultElement.h b/mailnews/base/search/public/nsMsgResultElement.h
new file mode 100644
index 000000000..384436299
--- /dev/null
+++ b/mailnews/base/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:
+ 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/mailnews/base/search/public/nsMsgSearchAdapter.h b/mailnews/base/search/public/nsMsgSearchAdapter.h
new file mode 100644
index 000000000..0b1cd71da
--- /dev/null
+++ b/mailnews/base/search/public/nsMsgSearchAdapter.h
@@ -0,0 +1,218 @@
+/* -*- 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 "nsStringGlue.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*, nsISupportsArray *);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGSEARCHADAPTER
+
+ nsIMsgSearchScopeTerm *m_scope;
+ nsCOMPtr<nsISupportsArray> m_searchTerms; /* linked list of criteria terms */
+
+ bool m_abortCalled;
+ nsString m_defaultCharset;
+ bool m_forceAsciiSearch;
+
+ static nsresult EncodeImap (char **ppEncoding,
+ nsISupportsArray *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 negotion 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/mailnews/base/search/public/nsMsgSearchBoolExpression.h b/mailnews/base/search/public/nsMsgSearchBoolExpression.h
new file mode 100644
index 000000000..c65e5c24a
--- /dev/null
+++ b/mailnews/base/search/public/nsMsgSearchBoolExpression.h
@@ -0,0 +1,107 @@
+/* -*- 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
+ 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 char *headers, uint32_t headerSize,
+ 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/mailnews/base/search/public/nsMsgSearchCore.idl b/mailnews/base/search/public/nsMsgSearchCore.idl
new file mode 100644
index 000000000..6431aa8c3
--- /dev/null
+++ b/mailnews/base/search/public/nsMsgSearchCore.idl
@@ -0,0 +1,222 @@
+/* -*- 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 {
+ 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 {
+ 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 Label = 48; /* mail only...can search by label */
+ 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 {
+ 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 {
+ 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 nsMsgSearchTypeValue;
+
+
+/* Used to specify type of search to be performed */
+[scriptable,uuid(964b7f32-304e-11d3-ae13-00a0c900d445)]
+interface nsMsgSearchType {
+ const nsMsgSearchTypeValue None = 0;
+ const nsMsgSearchTypeValue RootDSE = 1;
+ const nsMsgSearchTypeValue Normal = 2;
+ const nsMsgSearchTypeValue LdapVLV = 3;
+ const nsMsgSearchTypeValue NameCompletion = 4;
+};
+
+typedef long nsMsgSearchBooleanOperator;
+
+[scriptable, uuid(a37f3f4a-304e-11d3-8f94-00a0c900d445)]
+interface nsMsgSearchBooleanOp {
+ const nsMsgSearchBooleanOperator BooleanOR = 0;
+ const nsMsgSearchBooleanOperator BooleanAND = 1;
+};
+
+/* Use this to specify the value of a search term */
+
+[ptr] native nsMsgSearchValue(nsMsgSearchValue);
+
+%{C++
+#include "nsStringGlue.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;
+ nsMsgLabelValue label;
+ uint32_t junkStatus;
+ uint32_t junkPercent;
+ } u;
+ char *string;
+ 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::Label || _a == nsMsgSearchAttrib::JunkStatus || \
+ _a == nsMsgSearchAttrib::FolderFlag || _a == nsMsgSearchAttrib::Uint32HdrProperty || \
+ _a == nsMsgSearchAttrib::JunkPercent || _a == nsMsgSearchAttrib::HasAttachmentStatus))
+%}
+
+[ptr] native nsSearchMenuItem(nsSearchMenuItem);
+
diff --git a/mailnews/base/search/public/nsMsgSearchScopeTerm.h b/mailnews/base/search/public/nsMsgSearchScopeTerm.h
new file mode 100644
index 000000000..74afd8d19
--- /dev/null
+++ b/mailnews/base/search/public/nsMsgSearchScopeTerm.h
@@ -0,0 +1,45 @@
+/* -*- 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 "nsIWeakReference.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 (nsISupportsArray *termList);
+
+ char *GetStatusBarName ();
+
+ nsMsgSearchScopeValue m_attribute;
+ char *m_name;
+ 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/mailnews/base/search/public/nsMsgSearchTerm.h b/mailnews/base/search/public/nsMsgSearchTerm.h
new file mode 100644
index 000000000..ffcdc9539
--- /dev/null
+++ b/mailnews/base/search/public/nsMsgSearchTerm.h
@@ -0,0 +1,85 @@
+/* -*- 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/mailnews/base/search/src/Bogofilter.sfd b/mailnews/base/search/src/Bogofilter.sfd
new file mode 100644
index 000000000..a29a818e6
--- /dev/null
+++ b/mailnews/base/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/mailnews/base/search/src/DSPAM.sfd b/mailnews/base/search/src/DSPAM.sfd
new file mode 100644
index 000000000..40bc00df7
--- /dev/null
+++ b/mailnews/base/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/mailnews/base/search/src/Habeas.sfd b/mailnews/base/search/src/Habeas.sfd
new file mode 100644
index 000000000..ceffff16e
--- /dev/null
+++ b/mailnews/base/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/mailnews/base/search/src/POPFile.sfd b/mailnews/base/search/src/POPFile.sfd
new file mode 100644
index 000000000..a791705aa
--- /dev/null
+++ b/mailnews/base/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/mailnews/base/search/src/SpamAssassin.sfd b/mailnews/base/search/src/SpamAssassin.sfd
new file mode 100644
index 000000000..d8d0ecdb1
--- /dev/null
+++ b/mailnews/base/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/mailnews/base/search/src/SpamCatcher.sfd b/mailnews/base/search/src/SpamCatcher.sfd
new file mode 100644
index 000000000..d16cd80a2
--- /dev/null
+++ b/mailnews/base/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/mailnews/base/search/src/SpamPal.sfd b/mailnews/base/search/src/SpamPal.sfd
new file mode 100644
index 000000000..830b1937b
--- /dev/null
+++ b/mailnews/base/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/mailnews/base/search/src/moz.build b/mailnews/base/search/src/moz.build
new file mode 100644
index 000000000..ae3a57fa5
--- /dev/null
+++ b/mailnews/base/search/src/moz.build
@@ -0,0 +1,33 @@
+# 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_COMPONENTS += [
+ 'nsMsgTraitService.js',
+ 'nsMsgTraitService.manifest',
+]
+
+FINAL_LIBRARY = 'mail'
+
+FINAL_TARGET_FILES.isp += [
+ 'Bogofilter.sfd',
+ 'DSPAM.sfd',
+ 'POPFile.sfd',
+ 'SpamAssassin.sfd',
+ 'SpamPal.sfd',
+]
diff --git a/mailnews/base/search/src/nsMsgBodyHandler.cpp b/mailnews/base/search/src/nsMsgBodyHandler.cpp
new file mode 100644
index 000000000..873713bbb
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgBodyHandler.cpp
@@ -0,0 +1,487 @@
+/* -*- 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 = NULL;
+ 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_headersSize = headersSize;
+ m_Filtering = Filtering;
+
+ Initialize();
+
+ if (m_Filtering)
+ m_headers = headers;
+ else
+ OpenLocalFolder(); // if nothing else applies, then we must be a POP folder file
+}
+
+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_stripHtml = true;
+ m_partIsHtml = false;
+ m_base64part = 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);
+ }
+
+ 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.get());| below.
+ // ApplyTransformations(buf, buf.Length(), eatThisLine, buf);
+ // Avoid spurious failures
+ eatThisLine = false;
+ }
+ }
+ else
+ {
+ buf.Truncate();
+ eatThisLine = true; // We have no content...
+ }
+
+ // 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;
+
+ return buf.Length();
+ }
+
+ if (!m_partIsText)
+ {
+ // Ignore non-text parts
+ buf.Truncate();
+ eatThisLine = true;
+ return 0;
+ }
+
+ if (m_base64part)
+ {
+ // We need to keep track of all lines to parse base64encoded...
+ buf.Append(line.get());
+ eatThisLine = true;
+ return buf.Length();
+ }
+
+ // ... but there's no point if we're not parsing base64.
+ buf.Assign(line);
+ if (m_stripHtml && m_partIsHtml)
+ {
+ StripHtml (buf);
+ }
+
+ 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, NS_LITERAL_CSTRING("content-type:")))
+ {
+ if (lowerCaseLine.Find("text/html", CaseInsensitiveCompare) != -1)
+ {
+ m_partIsText = true;
+ m_partIsHtml = true;
+ }
+ else if (lowerCaseLine.Find("multipart/", CaseInsensitiveCompare) != -1)
+ {
+ if (m_isMultipart)
+ {
+ // Nested multipart, get ready for new headers.
+ m_base64part = false;
+ m_pastPartHeaders = false;
+ m_partIsHtml = false;
+ m_partIsText = false;
+ }
+ m_isMultipart = true;
+ m_partCharset.Truncate();
+ }
+ else if (lowerCaseLine.Find("message/", CaseInsensitiveCompare) != -1)
+ {
+ // Initialise again.
+ m_base64part = 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/", CaseInsensitiveCompare) != -1)
+ m_partIsText = true;
+ else if (lowerCaseLine.Find("text/", CaseInsensitiveCompare) == -1)
+ m_partIsText = false; // We have disproven our assumption.
+ }
+
+ int32_t start;
+ if (m_isMultipart &&
+ (start = lowerCaseLine.Find("boundary=", CaseInsensitiveCompare)) != -1)
+ {
+ 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.Assign("--");
+ boundary.Append(Substring(line, start, end-start));
+ if (!m_boundaries.Contains(boundary))
+ m_boundaries.AppendElement(boundary);
+ }
+
+ if (m_isMultipart &&
+ (start = lowerCaseLine.Find("charset=", CaseInsensitiveCompare)) != -1)
+ {
+ 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,
+ NS_LITERAL_CSTRING("content-transfer-encoding:")) &&
+ lowerCaseLine.Find(ENCODING_BASE64, CaseInsensitiveCompare) != 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)
+ pBufInOut.Adopt(decodedBody);
+
+ int32_t offset = pBufInOut.FindChar('\n');
+ while (offset != -1) {
+ pBufInOut.Replace(offset, 1, ' ');
+ offset = pBufInOut.FindChar('\n', offset);
+ }
+ offset = pBufInOut.FindChar('\r');
+ while (offset != -1) {
+ pBufInOut.Replace(offset, 1, ' ');
+ offset = pBufInOut.FindChar('\r', offset);
+ }
+}
+
diff --git a/mailnews/base/search/src/nsMsgFilter.cpp b/mailnews/base/search/src/nsMsgFilter.cpp
new file mode 100644
index 000000000..e94240f29
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgFilter.cpp
@@ -0,0 +1,1057 @@
+/* -*- 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 "nsMsgBaseCID.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 "nsIOutputStream.h"
+#include "nsIStringBundle.h"
+#include "nsDateTimeFormatCID.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIMsgFilterService.h"
+#include "nsIMutableArray.h"
+#include "prmem.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Services.h"
+
+static const char *kImapPrefix = "//imap:";
+static const char *kWhitespace = "\b\t\r\n ";
+
+nsMsgRuleAction::nsMsgRuleAction()
+{
+}
+
+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::SetLabel(nsMsgLabelValue aLabel)
+{
+ NS_ENSURE_TRUE(m_type == nsMsgFilterAction::Label,
+ NS_ERROR_ILLEGAL_VALUE);
+ m_label = aLabel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgRuleAction::GetLabel(nsMsgLabelValue *aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ NS_ENSURE_TRUE(m_type == nsMsgFilterAction::Label, NS_ERROR_ILLEGAL_VALUE);
+ *aResult = m_label;
+ 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(NS_MSGFILTERSERVICE_CONTRACTID, &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_temporary(false),
+ m_unparseable(false),
+ m_filterList(nullptr),
+ m_expressionTree(nullptr)
+{
+ nsresult rv = NS_NewISupportsArray(getter_AddRefs(m_termList));
+ if (NS_FAILED(rv))
+ NS_ASSERTION(false, "Failed to allocate a nsISupportsArray for nsMsgFilter");
+
+ m_type = nsMsgFilterType::InboxRule | nsMsgFilterType::Manual;
+}
+
+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;
+ return m_termList->AppendElement(static_cast<nsISupports*>(aTerm));
+}
+
+NS_IMETHODIMP
+nsMsgFilter::CreateTerm(nsIMsgSearchTerm **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ nsMsgSearchTerm *term = new nsMsgSearchTerm;
+ NS_ENSURE_TRUE(term, NS_ERROR_OUT_OF_MEMORY);
+
+ *aResult = static_cast<nsIMsgSearchTerm*>(term);
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFilter::CreateAction(nsIMsgRuleAction **aAction)
+{
+ NS_ENSURE_ARG_POINTER(aAction);
+ nsMsgRuleAction *action = new nsMsgRuleAction;
+ NS_ENSURE_TRUE(action, NS_ERROR_OUT_OF_MEMORY);
+
+ *aAction = static_cast<nsIMsgRuleAction*>(action);
+ NS_ADDREF(*aAction);
+ 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(nsIArray **aActionList)
+{
+ NS_ENSURE_ARG_POINTER(aActionList);
+
+ uint32_t numActions;
+ nsresult rv = GetActionCount(&numActions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> orderedActions(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // hold separate pointers into the action list
+ uint32_t nextIndexForNormal = 0, nextIndexForCopy = 0, nextIndexForMove = 0;
+ for (uint32_t index = 0; index < numActions; ++index)
+ {
+ nsCOMPtr<nsIMsgRuleAction> action;
+ rv = GetActionAt(index, getter_AddRefs(action));
+ if (NS_FAILED(rv) || !action)
+ continue;
+
+ nsMsgRuleActionType actionType;
+ action->GetType(&actionType);
+ switch (actionType)
+ {
+ case nsMsgFilterAction::FetchBodyFromPop3Server:
+ {
+ // always insert in front
+ rv = orderedActions->InsertElementAt(action, 0, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ ++nextIndexForNormal;
+ ++nextIndexForCopy;
+ ++nextIndexForMove;
+ break;
+ }
+
+ case nsMsgFilterAction::CopyToFolder:
+ {
+ // insert into copy actions block, in order of appearance
+ rv = orderedActions->InsertElementAt(action, nextIndexForCopy, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ ++nextIndexForCopy;
+ ++nextIndexForMove;
+ break;
+ }
+
+ case nsMsgFilterAction::MoveToFolder:
+ case nsMsgFilterAction::Delete:
+ {
+ // insert into move/delete action block
+ rv = orderedActions->InsertElementAt(action, nextIndexForMove, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ ++nextIndexForMove;
+ break;
+ }
+
+ case nsMsgFilterAction::StopExecution:
+ {
+ // insert into stop action block
+ rv = orderedActions->AppendElement(action, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ }
+
+ default:
+ {
+ // insert into normal action block, in order of appearance
+ rv = orderedActions->InsertElementAt(action, nextIndexForNormal, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ ++nextIndexForNormal;
+ ++nextIndexForCopy;
+ ++nextIndexForMove;
+ break;
+ }
+ }
+ }
+
+ orderedActions.forget(aActionList);
+ 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(*aAction = m_actionList[aIndex], NS_ERROR_ILLEGAL_VALUE);
+ NS_IF_ADDREF(*aAction);
+ 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 */
+{
+ nsCOMPtr<nsIMsgSearchTerm> term;
+ nsresult rv = m_termList->QueryElementAt(termIndex, NS_GET_IID(nsIMsgSearchTerm),
+ (void **)getter_AddRefs(term));
+ if (NS_SUCCEEDED(rv) && term)
+ {
+ 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(nsISupportsArray **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ // caller can change m_termList, which can invalidate m_expressionTree.
+ delete m_expressionTree;
+ m_expressionTree = nullptr;
+ NS_IF_ADDREF(*aResult = m_termList);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilter::SetSearchTerms(nsISupportsArray *aSearchList)
+{
+ delete m_expressionTree;
+ m_expressionTree = nullptr;
+ m_termList = aSearchList;
+ 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;
+}
+
+#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))
+// Does this need to be localizable?
+#define LOG_ENTRY_TIMESTAMP "[$S] "
+
+// 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 char *aErrmsg)
+{
+ NS_ENSURE_ARG_POINTER(aFilterAction);
+ NS_ENSURE_ARG_POINTER(aMsgHdr);
+
+ NS_ENSURE_TRUE(m_filterList, NS_OK);
+ nsCOMPtr <nsIOutputStream> logStream;
+ nsresult rv = m_filterList->GetLogStream(getter_AddRefs(logStream));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ 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);
+
+ if (!mDateFormatter)
+ {
+ mDateFormatter = do_CreateInstance(NS_DATETIMEFORMAT_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!mDateFormatter)
+ {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ mDateFormatter->FormatPRExplodedTime(nullptr, kDateFormatShort,
+ kTimeFormatSeconds, &exploded,
+ dateValue);
+
+ (void)aMsgHdr->GetMime2DecodedAuthor(authorValue);
+ (void)aMsgHdr->GetMime2DecodedSubject(subjectValue);
+
+ nsCString buffer;
+#ifdef MOZILLA_INTERNAL_API
+ // 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);
+#endif
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ 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))
+ {
+
+ // Let us put "Filter Action Failed: "%s" with error code=%s while attempting: " inside bundle.
+ // 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) ;
+ NS_ConvertASCIItoUTF16 tErrmsg16(aErrmsg) ;
+
+ const char16_t *logErrorFormatStrings[2] = { tErrmsg16.get(), tcode16.get()};
+ nsString filterFailureWarningPrefix;
+ rv = bundle->FormatStringFromName(
+ u"filterFailureWarningPrefix",
+ logErrorFormatStrings, 2,
+ getter_Copies(filterFailureWarningPrefix));
+ NS_ENSURE_SUCCESS(rv, rv);
+ buffer += NS_ConvertUTF16toUTF8(filterFailureWarningPrefix);
+ buffer += "\n";
+ }
+
+ const char16_t *filterLogDetectFormatStrings[4] = { filterName.get(), authorValue.get(), subjectValue.get(), dateValue.get() };
+ nsString filterLogDetectStr;
+ rv = bundle->FormatStringFromName(
+ u"filterLogDetectStr",
+ filterLogDetectFormatStrings, 4,
+ getter_Copies(filterLogDetectStr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ buffer += NS_ConvertUTF16toUTF8(filterLogDetectStr);
+ buffer += "\n";
+
+ if (actionType == nsMsgFilterAction::MoveToFolder ||
+ actionType == nsMsgFilterAction::CopyToFolder)
+ {
+ nsCString actionFolderUri;
+ aFilterAction->GetTargetFolderUri(actionFolderUri);
+ NS_ConvertASCIItoUTF16 actionFolderUriValue(actionFolderUri);
+
+ nsCString msgId;
+ aMsgHdr->GetMessageId(getter_Copies(msgId));
+ NS_ConvertASCIItoUTF16 msgIdValue(msgId);
+
+ const char16_t *logMoveFormatStrings[2] = { msgIdValue.get(), actionFolderUriValue.get() };
+ nsString logMoveStr;
+ rv = bundle->FormatStringFromName(
+ (actionType == nsMsgFilterAction::MoveToFolder) ?
+ u"logMoveStr" : u"logCopyStr",
+ logMoveFormatStrings, 2,
+ getter_Copies(logMoveStr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ buffer += NS_ConvertUTF16toUTF8(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(
+ u"filterMissingCustomAction",
+ getter_Copies(filterActionName));
+ buffer += NS_ConvertUTF16toUTF8(filterActionName);
+ }
+ else
+ {
+ nsString actionValue;
+ nsAutoString filterActionID;
+ filterActionID = NS_LITERAL_STRING("filterAction");
+ filterActionID.AppendInt(actionType);
+ rv = bundle->GetStringFromName(filterActionID.get(), getter_Copies(actionValue));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ buffer += NS_ConvertUTF16toUTF8(actionValue);
+ }
+ buffer += "\n";
+
+ // Prepare timestamp
+ PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &exploded);
+ mDateFormatter->FormatPRExplodedTime(nullptr, kDateFormatShort,
+ kTimeFormatSeconds, &exploded,
+ dateValue);
+
+ nsCString timestampString(LOG_ENTRY_TIMESTAMP);
+ MsgReplaceSubstring(timestampString, "$S", NS_ConvertUTF16toUTF8(dateValue).get());
+
+ // XXX: Finally, here we have enough context and buffer
+ // (string) to display the filtering error if we want: for
+ // example, a sticky error message in status bar, etc.
+
+ 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");
+
+ rv = logStream->Write(timestampString.get(), timestampString.Length(), &writeCount);
+ NS_ENSURE_SUCCESS(rv,rv);
+ NS_ASSERTION(writeCount == timestampString.Length(), "failed to write out timestamp");
+
+ // 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>.
+ char *escapedBuffer = MsgEscapeHTML(buffer.get());
+ if (!escapedBuffer)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ uint32_t escapedBufferLen = strlen(escapedBuffer);
+ rv = logStream->Write(escapedBuffer, escapedBufferLen, &writeCount);
+ PR_Free(escapedBuffer);
+ 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;
+}
+
+NS_IMETHODIMP nsMsgFilter::LogRuleHit(nsIMsgRuleAction *aFilterAction,
+ nsIMsgDBHdr *aMsgHdr)
+{
+ return nsMsgFilter::LogRuleHitGeneric(aFilterAction, aMsgHdr, NS_OK, nullptr);
+}
+
+NS_IMETHODIMP nsMsgFilter::LogRuleHitFail(nsIMsgRuleAction *aFilterAction,
+ nsIMsgDBHdr *aMsgHdr,
+ nsresult aRcode,
+ const char *aErrMsg)
+{
+ return nsMsgFilter::LogRuleHitGeneric(aFilterAction, aMsgHdr, aRcode, aErrMsg);
+}
+
+NS_IMETHODIMP
+nsMsgFilter::MatchHdr(nsIMsgDBHdr *msgHdr, nsIMsgFolder *folder,
+ nsIMsgDatabase *db, const char *headers,
+ uint32_t headersSize, bool *pResult)
+{
+ NS_ENSURE_ARG_POINTER(folder);
+ NS_ENSURE_ARG_POINTER(msgHdr);
+ // use offlineMail because
+ nsCString folderCharset;
+ folder->GetCharset(folderCharset);
+ nsresult rv = nsMsgSearchOfflineMail::MatchTermsForFilter(msgHdr, m_termList,
+ folderCharset.get(), m_scope, db, headers, headersSize, &m_expressionTree, pResult);
+ return rv;
+}
+
+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;
+ nsresult rv = nsMsgI18NConvertToUnicode(nsMsgI18NFileSystemCharset(),
+ originalServerPath,
+ unicodeStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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, NS_LITERAL_CSTRING("imap:")))
+ localMailRoot = rootFolder;
+ else
+ {
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &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;
+ nsCOMPtr <nsIMsgFolder> localMailRootMsgFolder = do_QueryInterface(localMailRoot);
+ localMailRoot->GetURI(localRootURI);
+ nsCString destFolderUri;
+ destFolderUri.Assign( localRootURI);
+ // need to remove ".sbd" from moveValue, and perhaps escape it.
+ int32_t offset = moveValue.Find(".sbd/");
+ if (offset != -1)
+ moveValue.Cut(offset, 4);
+
+#ifdef XP_MACOSX
+ nsCString unescapedMoveValue;
+ MsgUnescapeString(moveValue, 0, unescapedMoveValue);
+ moveValue = unescapedMoveValue;
+#endif
+ destFolderUri.Append('/');
+ if (filterVersion == k45Version)
+ {
+ nsAutoString unicodeStr;
+ rv = nsMsgI18NConvertToUnicode(nsMsgI18NFileSystemCharset(),
+ moveValue, unicodeStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = NS_MsgEscapeEncodeURLPath(unicodeStr, moveValue);
+ }
+ destFolderUri.Append(moveValue);
+ localMailRootMsgFolder->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::Label:
+ {
+ nsMsgLabelValue label;
+ action->GetLabel(&label);
+ err = filterList->WriteIntAttr(nsIMsgFilterList::attribActionValue, label, 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;
+ }
+ }
+ // and here the fun begins - file out term list...
+ nsAutoCString condition;
+ err = MsgTermListToString(m_termList, condition);
+ if (NS_SUCCEEDED(err))
+ err = filterList->WriteStrAttr(nsIMsgFilterList::attribCondition, condition.get(), aStream);
+ return err;
+}
+
+// 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::Label, "Label"},
+ { 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/mailnews/base/search/src/nsMsgFilter.h b/mailnews/base/search/src/nsMsgFilter.h
new file mode 100644
index 000000000..077baa2ff
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgFilter.h
@@ -0,0 +1,103 @@
+/* -*- 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 "nsIDateTimeFormat.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 */
+ nsMsgLabelValue m_label; /* label 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 char *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 */
+ nsCOMPtr<nsISupportsArray> m_termList; /* linked list of 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;
+ nsCOMPtr<nsIDateTimeFormat> mDateFormatter;
+};
+
+#endif
diff --git a/mailnews/base/search/src/nsMsgFilterList.cpp b/mailnews/base/search/src/nsMsgFilterList.cpp
new file mode 100644
index 000000000..d5b93fdc2
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgFilterList.cpp
@@ -0,0 +1,1198 @@
+/* -*- 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/. */
+
+// this file implements the nsMsgFilterList interface
+
+#include "nsTextFormatter.h"
+
+#include "msgCore.h"
+#include "nsMsgFilterList.h"
+#include "nsMsgFilter.h"
+#include "nsIMsgFilterHitNotify.h"
+#include "nsMsgUtils.h"
+#include "nsMsgSearchTerm.h"
+#include "nsStringGlue.h"
+#include "nsMsgBaseCID.h"
+#include "nsIMsgFilterService.h"
+#include "nsMsgSearchScopeTerm.h"
+#include "nsNetUtil.h"
+#include "nsIInputStream.h"
+#include "nsMsgI18N.h"
+#include "nsMemory.h"
+#include "prmem.h"
+#include "mozilla/ArrayUtils.h"
+#include <ctype.h>
+
+// unicode "%s" format string
+static const char16_t unicodeFormatter[] = {
+ (char16_t)'%',
+ (char16_t)'s',
+ (char16_t)0,
+};
+
+// Marker for EOF or failure during read
+#define EOF_CHAR -1
+
+nsMsgFilterList::nsMsgFilterList() :
+ m_fileVersion(0)
+{
+ m_loggingEnabled = false;
+ m_startWritingToBuffer = false;
+ m_temporaryList = false;
+ m_curFilter = nullptr;
+}
+
+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);
+
+ nsMsgFilter *filter = new nsMsgFilter;
+ NS_ENSURE_TRUE(filter, NS_ERROR_OUT_OF_MEMORY);
+
+ NS_ADDREF(*aFilter = filter);
+
+ filter->SetFilterName(name);
+ filter->SetFilterList(this);
+
+ return NS_OK;
+}
+
+NS_IMPL_GETSET(nsMsgFilterList, LoggingEnabled, bool, m_loggingEnabled)
+
+NS_IMETHODIMP nsMsgFilterList::GetFolder(nsIMsgFolder **aFolder)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+
+ *aFolder = m_folder;
+ NS_IF_ADDREF(*aFolder);
+ 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;
+}
+
+nsresult nsMsgFilterList::TruncateLog()
+{
+ // This will flush and close the stream.
+ nsresult rv = SetLogStream(nullptr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIFile> file;
+ rv = GetLogFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ file->Remove(false);
+
+ return EnsureLogFile(file);
+}
+
+NS_IMETHODIMP nsMsgFilterList::ClearLog()
+{
+ bool loggingEnabled = m_loggingEnabled;
+
+ // disable logging while clearing
+ m_loggingEnabled = false;
+
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ TruncateLog();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to truncate filter log");
+
+ m_loggingEnabled = 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 <nsIMsgFolder> folder;
+ nsresult rv = GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ rv = 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 = 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.Equals("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.Append(NS_LITERAL_STRING(".htm"));
+
+ rv = filterLogFile->SetLeafName(filterLogName);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ NS_IF_ADDREF(*aFile = filterLogFile);
+ }
+ else {
+ rv = server->GetLocalPath(aFile);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = (*aFile)->AppendNative(NS_LITERAL_CSTRING("filterlog.html"));
+ 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) {
+ // will flush
+ nsresult rv = m_logStream->Close();
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ m_logStream = aLogStream;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFilterList::GetLogStream(nsIOutputStream **aLogStream)
+{
+ NS_ENSURE_ARG_POINTER(aLogStream);
+
+ nsresult rv;
+
+ if (!m_logStream) {
+ nsCOMPtr <nsIFile> logFile;
+ rv = GetLogFile(getter_AddRefs(logFile));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // append to the end of the log file
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(m_logStream),
+ logFile,
+ PR_CREATE_FILE | PR_WRONLY | PR_APPEND,
+ 0666);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!m_logStream)
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_ADDREF(*aLogStream = m_logStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFilterList::ApplyFiltersToHdr(nsMsgFilterTypeType filterType,
+ nsIMsgDBHdr *msgHdr,
+ nsIMsgFolder *folder,
+ nsIMsgDatabase *db,
+ const char*headers,
+ uint32_t headersSize,
+ nsIMsgFilterHitNotify *listener,
+ nsIMsgWindow *msgWindow)
+{
+ nsCOMPtr<nsIMsgFilter> filter;
+ uint32_t filterCount = 0;
+ nsresult rv = GetFilterCount(&filterCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgSearchScopeTerm* scope = new nsMsgSearchScopeTerm(nullptr, nsMsgSearchScope::offlineMail, folder);
+ scope->AddRef();
+ if (!scope) return NS_ERROR_OUT_OF_MEMORY;
+
+ 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)
+ continue;
+
+ filter->GetFilterType(&curFilterType);
+ if (curFilterType & filterType)
+ {
+ nsresult matchTermStatus = NS_OK;
+ bool result;
+
+ filter->SetScope(scope);
+ matchTermStatus = filter->MatchHdr(msgHdr, folder, db, headers, headersSize, &result);
+ filter->SetScope(nullptr);
+ if (NS_SUCCEEDED(matchTermStatus) && result && listener)
+ {
+ bool applyMore = true;
+
+ rv = listener->ApplyFilterHit(filter, msgWindow, &applyMore);
+ if (NS_FAILED(rv) || !applyMore)
+ break;
+ }
+ }
+ }
+ }
+ scope->Release();
+ 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(NS_MSGFILTERSERVICE_CONTRACTID, &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;
+ nsresult rv = aStream->Read(&newChar, 1, &bytesRead);
+ if (NS_FAILED(rv) || !bytesRead)
+ return EOF_CHAR;
+ uint64_t bytesAvailable;
+ rv = aStream->Available(&bytesAvailable);
+ if (NS_FAILED(rv))
+ return EOF_CHAR;
+ else
+ {
+ 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.Equals("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(nsIInputStream *aStream)
+{
+ nsresult err = NS_OK;
+ uint64_t bytesAvailable;
+
+ nsCOMPtr<nsIInputStream> bufStream;
+ err = NS_NewBufferedInputStream(getter_AddRefs(bufStream), aStream, 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);
+ m_unparsedFilterBuffer.Truncate(); //we are going to buffer each filter as we read them, make sure no garbage is there
+ 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));
+ if (m_fileVersion == k45Version)
+ {
+ nsAutoString unicodeStr;
+ err = nsMsgI18NConvertToUnicode(nsMsgI18NFileSystemCharset(),
+ value, unicodeStr);
+ if (NS_FAILED(err))
+ break;
+
+ filter->SetFilterName(unicodeStr);
+ }
+ else
+ {
+ // ### fix me - this is silly.
+ char16_t *unicodeString =
+ nsTextFormatter::smprintf(unicodeFormatter, value.get());
+ filter->SetFilterName(nsDependentString(unicodeString));
+ nsTextFormatter::smprintf_free(unicodeString);
+ }
+ 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::Label)
+ {
+ // upgrade label to corresponding tag/keyword
+ nsresult res;
+ int32_t labelInt = value.ToInteger(&res);
+ if (NS_SUCCEEDED(res))
+ {
+ nsAutoCString keyword("$label");
+ keyword.Append('0' + labelInt);
+ currentFilterAction->SetType(nsMsgFilterAction::AddTag);
+ currentFilterAction->SetStrValue(keyword);
+ }
+ }
+ 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;
+ err = nsMsgI18NConvertToUnicode(nsMsgI18NFileSystemCharset(),
+ value, unicodeStr);
+ if (NS_FAILED(err))
+ break;
+
+ char *utf8 = ToNewUTF8String(unicodeStr);
+ value.Assign(utf8);
+ free(utf8);
+ }
+ 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"))
+ {
+ nsMsgSearchTerm *newTerm = new nsMsgSearchTerm;
+
+ if (newTerm)
+ {
+ newTerm->m_matchAll = true;
+ aFilter->AppendTerm(newTerm);
+ }
+ return (newTerm) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ 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)
+ {
+ nsMsgSearchTerm *newTerm = new nsMsgSearchTerm;
+
+ if (newTerm)
+ {
+ /* 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()
+{
+}
+
+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))
+ {
+ *aResult = filter;
+ break;
+ }
+ }
+
+ NS_IF_ADDREF(*aResult);
+ 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;
+
+ nsCOMPtr <nsISupportsArray> searchTerms;
+ uint32_t numSearchTerms=0;
+ filter->GetSearchTerms(getter_AddRefs(searchTerms));
+ if (searchTerms)
+ searchTerms->Count(&numSearchTerms);
+ for (uint32_t i = 0; i < numSearchTerms; i++)
+ {
+ filter->GetTerm(i, &attrib, nullptr, nullptr, nullptr, arbitraryHeader);
+ if (!arbitraryHeader.IsEmpty())
+ {
+ if (m_arbitraryHeaders.IsEmpty())
+ m_arbitraryHeaders.Assign(arbitraryHeader);
+ else if (m_arbitraryHeaders.Find(arbitraryHeader, CaseInsensitiveCompare) == -1)
+ {
+ 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
+ bool loggingEnabled = false;
+ nsresult rv = GetLoggingEnabled(&loggingEnabled);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (loggingEnabled)
+ {
+ nsCOMPtr <nsIOutputStream> logStream;
+ rv = GetLogStream(getter_AddRefs(logStream));
+ if (NS_SUCCEEDED(rv) && logStream) {
+ rv = logStream->Flush();
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ }
+ return rv;
+}
+// ------------ End FilterList methods ------------------
diff --git a/mailnews/base/search/src/nsMsgFilterList.h b/mailnews/base/search/src/nsMsgFilterList.h
new file mode 100644
index 000000000..2bb441d38
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgFilterList.h
@@ -0,0 +1,75 @@
+/* -*- 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(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_filterFileName;
+ nsTArray<nsCOMPtr<nsIMsgFilter> > m_filters;
+ nsCString m_arbitraryHeaders;
+ nsCOMPtr<nsIFile> m_defaultFile;
+ nsCString m_unparsedFilterBuffer; //holds one entire filter unparsed
+
+private:
+ nsresult TruncateLog();
+ nsresult GetLogFile(nsIFile **aFile);
+ nsresult EnsureLogFile(nsIFile *file);
+ nsCOMPtr<nsIOutputStream> m_logStream;
+};
+
+#endif
diff --git a/mailnews/base/search/src/nsMsgFilterService.cpp b/mailnews/base/search/src/nsMsgFilterService.cpp
new file mode 100644
index 000000000..c8f52de5a
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgFilterService.cpp
@@ -0,0 +1,1216 @@
+/* -*- 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/. */
+
+// 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 "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIStringBundle.h"
+#include "nsIMsgSearchNotify.h"
+#include "nsIUrlListener.h"
+#include "nsIMsgCopyServiceListener.h"
+#include "nsIMsgLocalMailFolder.h"
+#include "nsIMsgDatabase.h"
+#include "nsIMsgHdr.h"
+#include "nsIDBFolderInfo.h"
+#include "nsIRDFService.h"
+#include "nsMsgBaseCID.h"
+#include "nsIMsgCopyService.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsISafeOutputStream.h"
+#include "nsIMsgComposeService.h"
+#include "nsMsgCompCID.h"
+#include "nsNetUtil.h"
+#include "nsMsgUtils.h"
+#include "nsIMutableArray.h"
+#include "nsIMsgMailSession.h"
+#include "nsArrayUtils.h"
+#include "nsCOMArray.h"
+#include "nsIMsgFilterCustomAction.h"
+#include "nsArrayEnumerator.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgSearchCustomTerm.h"
+#include "nsIMsgSearchTerm.h"
+#include "nsIMsgThread.h"
+#include "nsAutoPtr.h"
+#include "nsIMsgFilter.h"
+#include "nsIMsgOperationListener.h"
+#include "mozilla/Attributes.h"
+
+#define BREAK_IF_FAILURE(_rv, _text) if (NS_FAILED(_rv)) { \
+ NS_WARNING(_text); \
+ mFinalResult = _rv; \
+ break; \
+}
+
+#define CONTINUE_IF_FAILURE(_rv, _text) if (NS_FAILED(_rv)) { \
+ NS_WARNING(_text); \
+ mFinalResult = _rv; \
+ if (m_msgWindow && !ContinueExecutionPrompt()) \
+ return OnEndExecution(); \
+ continue; \
+}
+
+#define BREAK_IF_FALSE(_assertTrue, _text) if (!(_assertTrue)) { \
+ NS_WARNING(_text); \
+ mFinalResult = NS_ERROR_FAILURE; \
+ break; \
+}
+
+#define CONTINUE_IF_FALSE(_assertTrue, _text) if (!(_assertTrue)) { \
+ NS_WARNING(_text); \
+ mFinalResult = NS_ERROR_FAILURE; \
+ if (m_msgWindow && !ContinueExecutionPrompt()) \
+ return OnEndExecution(); \
+ continue; \
+}
+
+NS_IMPL_ISUPPORTS(nsMsgFilterService, nsIMsgFilterService)
+
+nsMsgFilterService::nsMsgFilterService()
+{
+}
+
+nsMsgFilterService::~nsMsgFilterService()
+{
+}
+
+NS_IMETHODIMP nsMsgFilterService::OpenFilterList(nsIFile *aFilterFile,
+ nsIMsgFolder *rootFolder,
+ nsIMsgWindow *aMsgWindow,
+ nsIMsgFilterList **resultFilterList)
+{
+ NS_ENSURE_ARG_POINTER(aFilterFile);
+ NS_ENSURE_ARG_POINTER(resultFilterList);
+
+ bool exists = false;
+ nsresult 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();
+ NS_ENSURE_TRUE(filterList, NS_ERROR_OUT_OF_MEMORY);
+ 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);
+ fileStream->Close();
+ fileStream = nullptr;
+ 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);
+ }
+
+ NS_ADDREF(*resultFilterList = filterList);
+ 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);
+
+ 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");
+ }
+ }
+ 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(NS_LITERAL_CSTRING("rulesbackup.dat"));
+ bool exists;
+ backupFile->Exists(&exists);
+ if (exists)
+ backupFile->Remove(false);
+
+ return aFilterFile->CopyToNative(localParentDir, NS_LITERAL_CSTRING("rulesbackup.dat"));
+}
+
+nsresult nsMsgFilterService::AlertBackingUpFilterFile(nsIMsgWindow *aMsgWindow)
+{
+ return ThrowAlertMsg("filterListBackUpMsg", aMsgWindow);
+}
+
+nsresult //Do not use this routine if you have to call it very often because it creates a new bundle each time
+nsMsgFilterService::GetStringFromBundle(const char *aMsgName, char16_t **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsCOMPtr <nsIStringBundle> bundle;
+ nsresult rv = GetFilterStringBundle(getter_AddRefs(bundle));
+ if (NS_SUCCEEDED(rv) && bundle)
+ rv = bundle->GetStringFromName(NS_ConvertASCIItoUTF16(aMsgName).get(), aResult);
+ return rv;
+
+}
+
+nsresult
+nsMsgFilterService::GetFilterStringBundle(nsIStringBundle **aBundle)
+{
+ NS_ENSURE_ARG_POINTER(aBundle);
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::services::GetStringBundleService();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> bundle;
+ if (bundleService)
+ bundleService->CreateBundle("chrome://messenger/locale/filter.properties",
+ getter_AddRefs(bundle));
+ NS_IF_ADDREF(*aBundle = bundle);
+ return NS_OK;
+}
+
+nsresult
+nsMsgFilterService::ThrowAlertMsg(const char*aMsgName, nsIMsgWindow *aMsgWindow)
+{
+ nsString alertString;
+ nsresult rv = GetStringFromBundle(aMsgName, getter_Copies(alertString));
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryInterface(aMsgWindow));
+ if (!msgWindow) {
+ nsCOMPtr<nsIMsgMailSession> mailSession ( do_GetService(NS_MSGMAILSESSION_CONTRACTID, &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, nsIArray *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;
+ nsCOMPtr<nsIArray> 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;
+ uint32_t m_numFolders;
+ nsTArray<nsMsgKey> m_searchHits;
+ nsCOMPtr<nsIMutableArray> 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,
+ nsIArray *aFolderList,
+ nsIMsgOperationListener *aCallback)
+{
+ m_curFilterIndex = m_curFolderIndex = m_nextAction = 0;
+ m_msgWindow = aMsgWindow;
+ m_filters = aFilterList;
+ m_folders = aFolderList;
+ m_filters->GetFilterCount(&m_numFilters);
+ m_folders->GetLength(&m_numFolders);
+
+ NS_ADDREF(this); // we own ourselves, and will release ourselves when execution is done.
+ mNeedsRelease = true;
+
+ m_searchHitHdrs = do_CreateInstance(NS_ARRAY_CONTRACTID);
+ m_callback = aCallback;
+ mFinalResult = NS_OK;
+}
+
+nsMsgFilterAfterTheFact::~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)
+ {
+ Release(); // release ourselves.
+ mNeedsRelease = false;
+ }
+ return rv;
+}
+
+nsresult nsMsgFilterAfterTheFact::RunNextFilter()
+{
+ nsresult rv = NS_OK;
+ while (true)
+ {
+ m_curFilter = nullptr;
+ if (m_curFilterIndex >= m_numFilters)
+ break;
+ BREAK_IF_FALSE(m_filters, "Missing filters");
+ rv = m_filters->GetFilterAt(m_curFilterIndex++, getter_AddRefs(m_curFilter));
+ CONTINUE_IF_FAILURE(rv, "Could not get filter at index");
+
+ nsCOMPtr <nsISupportsArray> searchTerms;
+ rv = m_curFilter->GetSearchTerms(getter_AddRefs(searchTerms));
+ CONTINUE_IF_FAILURE(rv, "Could not get searchTerms");
+
+ if (m_searchSession)
+ m_searchSession->UnregisterListener(this);
+ m_searchSession = do_CreateInstance(NS_MSGSEARCHSESSION_CONTRACTID, &rv);
+ BREAK_IF_FAILURE(rv, "Failed to get search session");
+
+ nsMsgSearchScopeValue searchScope = nsMsgSearchScope::offlineMail;
+ uint32_t termCount;
+ searchTerms->Count(&termCount);
+ for (uint32_t termIndex = 0; termIndex < termCount; termIndex++)
+ {
+ nsCOMPtr <nsIMsgSearchTerm> term;
+ nsresult rv = searchTerms->QueryElementAt(termIndex, NS_GET_IID(nsIMsgSearchTerm), getter_AddRefs(term));
+ BREAK_IF_FAILURE(rv, "Could not get search term");
+ 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
+ }
+ m_curFilter = nullptr;
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Search failed");
+ return AdvanceToNextFolder();
+}
+
+nsresult 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_numFolders)
+ // final end of nsMsgFilterAfterTheFact object
+ return OnEndExecution();
+
+ // reset the filter index to apply all filters to this new folder
+ m_curFilterIndex = 0;
+ m_nextAction = 0;
+ rv = m_folders->QueryElementAt(m_curFolderIndex++, NS_GET_IID(nsIMsgFolder), getter_AddRefs(m_curFolder));
+ CONTINUE_IF_FAILURE(rv, "Could not get next folder");
+
+ // 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");
+
+ 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);
+ NS_ENSURE_TRUE(m_searchHitHdrs, NS_ERROR_NOT_INITIALIZED);
+
+ nsMsgKey msgKey;
+ header->GetMessageKey(&msgKey);
+
+ // 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))
+ return NS_OK;
+
+ m_searchHits.AppendElement(msgKey);
+ m_searchHitHdrs->AppendElement(header, false);
+ return NS_OK;
+}
+
+// Continue after an async operation.
+NS_IMETHODIMP nsMsgFilterAfterTheFact::OnSearchDone(nsresult status)
+{
+ 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()
+{
+ 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();
+ BREAK_IF_FALSE(m_searchHitHdrs, "No search headers object");
+ // 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);
+
+ nsCOMPtr<nsIArray> actionList;
+ rv = m_curFilter->GetSortedActionList(getter_AddRefs(actionList));
+ BREAK_IF_FAILURE(rv, "Could not get action list for filter");
+
+ uint32_t numActions;
+ actionList->GetLength(&numActions);
+
+ // We start from m_nextAction to allow us to continue applying actions
+ // after the return from an async copy.
+ while (m_nextAction < numActions)
+ {
+ nsCOMPtr<nsIMsgRuleAction>filterAction(do_QueryElementAt(actionList, m_nextAction++, &rv));
+ CONTINUE_IF_FAILURE(rv, "actionList cannot QI element");
+
+ nsMsgRuleActionType actionType;
+ rv = filterAction->GetType(&actionType);
+ CONTINUE_IF_FAILURE(rv, "Could not get type for filter action");
+
+ nsCString actionTargetFolderUri;
+ if (actionType == nsMsgFilterAction::MoveToFolder ||
+ actionType == nsMsgFilterAction::CopyToFolder)
+ {
+ rv = filterAction->GetTargetFolderUri(actionTargetFolderUri);
+ CONTINUE_IF_FALSE(NS_SUCCEEDED(rv) && !actionTargetFolderUri.IsEmpty(),
+ "actionTargetFolderUri is empty");
+ }
+
+ if (loggingEnabled)
+ {
+ for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ m_searchHitHdrs->QueryElementAt(msgIndex, NS_GET_IID(nsIMsgDBHdr), getter_AddRefs(msgHdr));
+ if (msgHdr)
+ (void)m_curFilter->LogRuleHit(filterAction, msgHdr);
+ else
+ NS_WARNING("could not QI element to nsIMsgDBHdr");
+ }
+ }
+
+ // 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.
+ m_curFolder->DeleteMessages(m_searchHitHdrs, m_msgWindow, false, false, nullptr, false /*allow Undo*/ );
+
+ // don't allow any more filters on this message
+ m_stopFiltering.AppendElements(m_searchHits);
+ for (uint32_t i = 0; i < m_searchHits.Length(); i++)
+ m_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.
+ MOZ_FALLTHROUGH;
+ case nsMsgFilterAction::CopyToFolder:
+ {
+ nsCString uri;
+ m_curFolder->GetURI(uri);
+ if (!actionTargetFolderUri.IsEmpty() &&
+ !uri.Equals(actionTargetFolderUri))
+ {
+ nsCOMPtr<nsIRDFService> rdf = do_GetService("@mozilla.org/rdf/rdf-service;1",&rv);
+ nsCOMPtr<nsIRDFResource> res;
+ rv = rdf->GetResource(actionTargetFolderUri, getter_AddRefs(res));
+ CONTINUE_IF_FAILURE(rv, "Could not get resource for action folder");
+
+ nsCOMPtr<nsIMsgFolder> destIFolder(do_QueryInterface(res, &rv));
+ CONTINUE_IF_FAILURE(rv, "Could not QI resource to folder");
+
+ bool canFileMessages = true;
+ nsCOMPtr<nsIMsgFolder> parentFolder;
+ destIFolder->GetParent(getter_AddRefs(parentFolder));
+ if (parentFolder)
+ destIFolder->GetCanFileMessages(&canFileMessages);
+ if (!parentFolder || !canFileMessages)
+ {
+ m_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;
+ mFinalResult = NS_ERROR_FAILURE;
+ break;
+ }
+ nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
+ CONTINUE_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++)
+ m_curFolder->OrProcessingFlags(m_searchHits[i],
+ nsMsgProcessingFlags::FilterToMove);
+ }
+
+ rv = copyService->CopyMessages(m_curFolder, m_searchHitHdrs,
+ destIFolder, actionType == nsMsgFilterAction::MoveToFolder,
+ this, m_msgWindow, false);
+ CONTINUE_IF_FAILURE(rv, "CopyMessages failed");
+ return NS_OK; // OnStopCopy callback to continue;
+ }
+ else
+ NS_WARNING("Move or copy failed, empty or unchanged destination");
+ }
+ 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
+ m_curFolder->MarkMessagesRead(m_searchHitHdrs, true);
+ break;
+ case nsMsgFilterAction::MarkUnread:
+ m_curFolder->MarkMessagesRead(m_searchHitHdrs, false);
+ break;
+ case nsMsgFilterAction::MarkFlagged:
+ m_curFolder->MarkMessagesFlagged(m_searchHitHdrs, true);
+ break;
+ case nsMsgFilterAction::KillThread:
+ case nsMsgFilterAction::WatchThread:
+ {
+ for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ m_searchHitHdrs->QueryElementAt(msgIndex, NS_GET_IID(nsIMsgDBHdr), getter_AddRefs(msgHdr));
+ CONTINUE_IF_FALSE(msgHdr, "Could not get msg header");
+
+ nsCOMPtr<nsIMsgThread> msgThread;
+ nsMsgKey threadKey;
+ m_curFolderDB->GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(msgThread));
+ CONTINUE_IF_FALSE(msgThread, "Could not find msg thread");
+ msgThread->GetThreadKey(&threadKey);
+ if (actionType == nsMsgFilterAction::KillThread)
+ m_curFolderDB->MarkThreadIgnored(msgThread, threadKey, true, nullptr);
+ else
+ m_curFolderDB->MarkThreadWatched(msgThread, threadKey, true, nullptr);
+ }
+ }
+ break;
+ case nsMsgFilterAction::KillSubthread:
+ {
+ for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ m_searchHitHdrs->QueryElementAt(msgIndex, NS_GET_IID(nsIMsgDBHdr), getter_AddRefs(msgHdr));
+ CONTINUE_IF_FALSE(msgHdr, "Could not get msg header");
+ m_curFolderDB->MarkHeaderKilled(msgHdr, true, nullptr);
+ }
+ }
+ break;
+ case nsMsgFilterAction::ChangePriority:
+ {
+ nsMsgPriorityValue filterPriority;
+ filterAction->GetPriority(&filterPriority);
+ for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ m_searchHitHdrs->QueryElementAt(msgIndex, NS_GET_IID(nsIMsgDBHdr), getter_AddRefs(msgHdr));
+ CONTINUE_IF_FALSE(msgHdr, "Could not get msg header");
+ msgHdr->SetPriority(filterPriority);
+ }
+ }
+ break;
+ case nsMsgFilterAction::Label:
+ {
+ nsMsgLabelValue filterLabel;
+ filterAction->GetLabel(&filterLabel);
+ m_curFolder->SetLabelForMessages(m_searchHitHdrs, filterLabel);
+ }
+ break;
+ case nsMsgFilterAction::AddTag:
+ {
+ nsCString keyword;
+ filterAction->GetStrValue(keyword);
+ m_curFolder->AddKeywordsToMessages(m_searchHitHdrs, keyword);
+ }
+ break;
+ case nsMsgFilterAction::JunkScore:
+ {
+ nsAutoCString junkScoreStr;
+ int32_t junkScore;
+ filterAction->GetJunkScore(&junkScore);
+ junkScoreStr.AppendInt(junkScore);
+ m_curFolder->SetJunkScoreForMessages(m_searchHitHdrs, junkScoreStr);
+ }
+ break;
+ case nsMsgFilterAction::Forward:
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = m_curFolder->GetServer(getter_AddRefs(server));
+ CONTINUE_IF_FAILURE(rv, "Could not get server");
+ nsCString forwardTo;
+ filterAction->GetStrValue(forwardTo);
+ CONTINUE_IF_FALSE(!forwardTo.IsEmpty(), "blank forwardTo URI");
+ nsCOMPtr<nsIMsgComposeService> compService =
+ do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID, &rv);
+ CONTINUE_IF_FAILURE(rv, "Could not get compose service");
+
+ for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr(do_QueryElementAt(m_searchHitHdrs,
+ msgIndex));
+ if (msgHdr)
+ rv = compService->ForwardMessage(NS_ConvertASCIItoUTF16(forwardTo),
+ msgHdr, m_msgWindow, server,
+ nsIMsgComposeService::kForwardAsDefault);
+ CONTINUE_IF_FALSE(msgHdr && NS_SUCCEEDED(rv), "Forward action failed");
+ }
+ }
+ break;
+ case nsMsgFilterAction::Reply:
+ {
+ nsCString replyTemplateUri;
+ filterAction->GetStrValue(replyTemplateUri);
+ CONTINUE_IF_FALSE(!replyTemplateUri.IsEmpty(), "Empty reply template URI");
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = m_curFolder->GetServer(getter_AddRefs(server));
+ CONTINUE_IF_FAILURE(rv, "Could not get server");
+
+ nsCOMPtr<nsIMsgComposeService> compService = do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID, &rv);
+ CONTINUE_IF_FAILURE(rv, "Could not get compose service");
+ for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ m_searchHitHdrs->QueryElementAt(msgIndex, NS_GET_IID(nsIMsgDBHdr), getter_AddRefs(msgHdr));
+ CONTINUE_IF_FALSE(msgHdr, "Could not get msgHdr");
+ rv = compService->ReplyWithTemplate(msgHdr, replyTemplateUri.get(), m_msgWindow, server);
+ if (NS_FAILED(rv)) {
+ if (rv == NS_ERROR_ABORT) {
+ m_curFilter->LogRuleHitFail(filterAction, msgHdr, rv, "Sending reply aborted");
+ } else {
+ m_curFilter->LogRuleHitFail(filterAction, msgHdr, rv, "Error sending reply");
+ }
+ }
+ CONTINUE_IF_FAILURE(rv, "ReplyWithTemplate failed");
+ }
+ }
+ break;
+ case nsMsgFilterAction::DeleteFromPop3Server:
+ {
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_curFolder);
+ CONTINUE_IF_FALSE(localFolder, "Current folder not a local folder");
+ // This action ignores the deleteMailLeftOnServer preference
+ rv = localFolder->MarkMsgsOnPop3Server(m_searchHitHdrs, POP3_FORCE_DEL);
+ CONTINUE_IF_FAILURE(rv, "MarkMsgsOnPop3Server failed");
+
+ nsCOMPtr<nsIMutableArray> partialMsgs;
+ // Delete the partial headers. They're useless now
+ // that the server copy is being deleted.
+ for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr;
+ m_searchHitHdrs->QueryElementAt(msgIndex, NS_GET_IID(nsIMsgDBHdr), getter_AddRefs(msgHdr));
+ CONTINUE_IF_FALSE(msgHdr, "Could not get msgHdr");
+ uint32_t flags;
+ msgHdr->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::Partial)
+ {
+ if (!partialMsgs)
+ partialMsgs = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ CONTINUE_IF_FALSE(partialMsgs, "Could not create partialMsgs array");
+ partialMsgs->AppendElement(msgHdr, false);
+ m_stopFiltering.AppendElement(m_searchHits[msgIndex]);
+ m_curFolder->OrProcessingFlags(m_searchHits[msgIndex],
+ nsMsgProcessingFlags::FilterToMove);
+ }
+ }
+ if (partialMsgs)
+ {
+ m_curFolder->DeleteMessages(partialMsgs, m_msgWindow, true, false, nullptr, false);
+ CONTINUE_IF_FAILURE(rv, "Delete messages failed");
+ }
+ }
+ break;
+ case nsMsgFilterAction::FetchBodyFromPop3Server:
+ {
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_curFolder);
+ CONTINUE_IF_FALSE(localFolder, "current folder not local");
+ nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ CONTINUE_IF_FAILURE(rv, "Could not create messages array");
+ for (uint32_t msgIndex = 0; msgIndex < m_searchHits.Length(); msgIndex++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ m_searchHitHdrs->QueryElementAt(msgIndex, NS_GET_IID(nsIMsgDBHdr), getter_AddRefs(msgHdr));
+ CONTINUE_IF_FALSE(msgHdr, "Could not get msgHdr");
+ uint32_t flags = 0;
+ msgHdr->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::Partial)
+ messages->AppendElement(msgHdr, false);
+ }
+ uint32_t msgsToFetch;
+ messages->GetLength(&msgsToFetch);
+ if (msgsToFetch > 0)
+ {
+ rv = m_curFolder->DownloadMessagesForOffline(messages, m_msgWindow);
+ CONTINUE_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;
+ m_curFilter->GetFilterType(&filterType);
+ nsCOMPtr<nsIMsgFilterCustomAction> customAction;
+ rv = filterAction->GetCustomAction(getter_AddRefs(customAction));
+ CONTINUE_IF_FAILURE(rv, "Could not get custom action");
+
+ nsAutoCString value;
+ filterAction->GetStrValue(value);
+ bool isAsync = false;
+ customAction->GetIsAsync(&isAsync);
+ rv = customAction->Apply(m_searchHitHdrs, value, this,
+ filterType, m_msgWindow);
+ CONTINUE_IF_FAILURE(rv, "custom action failed to apply");
+ if (isAsync)
+ return NS_OK; // custom action should call ApplyFilter on callback
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ } while (false); // end error management block
+ return RunNextFilter();
+}
+
+NS_IMETHODIMP nsMsgFilterService::GetTempFilterList(nsIMsgFolder *aFolder, nsIMsgFilterList **aFilterList)
+{
+ NS_ENSURE_ARG_POINTER(aFilterList);
+
+ nsMsgFilterList *filterList = new nsMsgFilterList;
+ NS_ENSURE_TRUE(filterList, NS_ERROR_OUT_OF_MEMORY);
+ NS_ADDREF(*aFilterList = filterList);
+ (*aFilterList)->SetFolder(aFolder);
+ filterList->m_temporaryList = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFilterService::ApplyFiltersToFolders(nsIMsgFilterList *aFilterList,
+ nsIArray *aFolders,
+ nsIMsgWindow *aMsgWindow,
+ nsIMsgOperationListener *aCallback)
+{
+ NS_ENSURE_ARG_POINTER(aFilterList);
+ NS_ENSURE_ARG_POINTER(aFolders);
+
+ 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.AppendObject(aAction);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilterService::GetCustomActions(nsISimpleEnumerator** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ return NS_NewArrayEnumerator(aResult, mCustomActions);
+}
+
+NS_IMETHODIMP
+nsMsgFilterService::GetCustomAction(const nsACString & aId,
+ nsIMsgFilterCustomAction** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ for (int32_t i = 0; i < mCustomActions.Count(); i++)
+ {
+ nsAutoCString id;
+ nsresult rv = mCustomActions[i]->GetId(id);
+ if (NS_SUCCEEDED(rv) && aId.Equals(id))
+ {
+ NS_ADDREF(*aResult = mCustomActions[i]);
+ return NS_OK;
+ }
+ }
+ aResult = nullptr;
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMsgFilterService::AddCustomTerm(nsIMsgSearchCustomTerm *aTerm)
+{
+ mCustomTerms.AppendObject(aTerm);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilterService::GetCustomTerms(nsISimpleEnumerator** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ return NS_NewArrayEnumerator(aResult, mCustomTerms);
+}
+
+NS_IMETHODIMP
+nsMsgFilterService::GetCustomTerm(const nsACString& aId,
+ nsIMsgSearchCustomTerm** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ for (int32_t i = 0; i < mCustomTerms.Count(); i++)
+ {
+ nsAutoCString id;
+ nsresult rv = mCustomTerms[i]->GetId(id);
+ if (NS_SUCCEEDED(rv) && aId.Equals(id))
+ {
+ NS_ADDREF(*aResult = mCustomTerms[i]);
+ return NS_OK;
+ }
+ }
+ aResult = nullptr;
+ // we use a null result to indicate failure to find a term
+ 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,
+ nsIArray *aFolderList, nsIArray *aMsgHdrList,
+ nsMsgFilterTypeType aFilterType,
+ nsIMsgOperationListener *aCallback);
+
+protected:
+ virtual nsresult RunNextFilter();
+
+ nsCOMArray<nsIMsgDBHdr> m_msgHdrList;
+ nsMsgFilterTypeType m_filterType;
+};
+
+nsMsgApplyFiltersToMessages::nsMsgApplyFiltersToMessages(nsIMsgWindow *aMsgWindow,
+ nsIMsgFilterList *aFilterList,
+ nsIArray *aFolderList,
+ nsIArray *aMsgHdrList,
+ nsMsgFilterTypeType aFilterType,
+ nsIMsgOperationListener *aCallback)
+: nsMsgFilterAfterTheFact(aMsgWindow, aFilterList, aFolderList, aCallback),
+ m_filterType(aFilterType)
+{
+ nsCOMPtr<nsISimpleEnumerator> msgEnumerator;
+ if (NS_SUCCEEDED(aMsgHdrList->Enumerate(getter_AddRefs(msgEnumerator))))
+ {
+ uint32_t length;
+ if (NS_SUCCEEDED(aMsgHdrList->GetLength(&length)))
+ m_msgHdrList.SetCapacity(length);
+
+ bool hasMore;
+ while (NS_SUCCEEDED(msgEnumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> supports;
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ if (NS_SUCCEEDED(msgEnumerator->GetNext(getter_AddRefs(supports))) &&
+ (msgHdr = do_QueryInterface(supports)))
+ m_msgHdrList.AppendObject(msgHdr);
+ }
+ }
+}
+
+nsresult 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;
+
+ 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 (int32_t i = 0; i < m_msgHdrList.Count(); i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr = m_msgHdrList[i];
+ CONTINUE_IF_FALSE(msgHdr, "null msgHdr");
+
+ bool matched;
+ rv = m_curFilter->MatchHdr(msgHdr, m_curFolder, m_curFolderDB, nullptr, 0, &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.
+ }
+ }
+ m_curFilter = nullptr;
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to run filters");
+ // 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,
+ nsIArray *aMsgHdrList,
+ nsIMsgFolder *aFolder,
+ nsIMsgWindow *aMsgWindow,
+ nsIMsgOperationListener *aCallback)
+{
+ NS_ENSURE_ARG_POINTER(aFolder);
+
+ nsCOMPtr<nsIMsgFilterList> filterList;
+ nsresult rv = aFolder->GetFilterList(aMsgWindow, getter_AddRefs(filterList));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMutableArray> folderList(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ folderList->AppendElement(aFolder, false);
+
+ // Create our nsMsgApplyFiltersToMessages object which will be called when ApplyFiltersToHdr
+ // finds one or more filters that hit.
+ RefPtr<nsMsgApplyFiltersToMessages> filterExecutor =
+ new nsMsgApplyFiltersToMessages(aMsgWindow, filterList, folderList,
+ 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))
+ return ApplyFilter();
+
+ 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::services::GetStringBundleService();
+ 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;
+ const char16_t *formatStrings[] =
+ {
+ filterName.get()
+ };
+ nsresult rv = bundle->FormatStringFromName(u"continueFilterExecution",
+ formatStrings, 1, getter_Copies(confirmText));
+ if (NS_FAILED(rv))
+ return false;
+ bool returnVal = false;
+ (void) DisplayConfirmationPrompt(m_msgWindow, confirmText.get(), &returnVal);
+ 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/mailnews/base/search/src/nsMsgFilterService.h b/mailnews/base/search/src/nsMsgFilterService.h
new file mode 100644
index 000000000..8fdc6a5cb
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgFilterService.h
@@ -0,0 +1,46 @@
+/* -*- 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 "nsCOMArray.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, char16_t **aResult);
+ nsresult GetFilterStringBundle(nsIStringBundle **aBundle);
+
+protected:
+ virtual ~nsMsgFilterService();
+
+ nsCOMArray<nsIMsgFilterCustomAction> mCustomActions; // defined custom action list
+ nsCOMArray<nsIMsgSearchCustomTerm> mCustomTerms; // defined custom term list
+
+};
+
+#endif // _nsMsgFilterService_H_
+
diff --git a/mailnews/base/search/src/nsMsgImapSearch.cpp b/mailnews/base/search/src/nsMsgImapSearch.cpp
new file mode 100644
index 000000000..5b3b2698a
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgImapSearch.cpp
@@ -0,0 +1,1004 @@
+/* -*- 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 "msgCore.h"
+#include "nsMsgSearchAdapter.h"
+#include "nsMsgSearchScopeTerm.h"
+#include "nsMsgResultElement.h"
+#include "nsMsgSearchTerm.h"
+#include "nsIMsgHdr.h"
+#include "nsMsgSearchImap.h"
+#include "prmem.h"
+// Disable deprecation warnings generated by nsISupportsArray and associated
+// classes.
+#if defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#elif defined(_MSC_VER)
+#pragma warning (disable : 4996)
+#endif
+#include "nsISupportsArray.h"
+// Implementation of search for IMAP mail folders
+
+
+nsMsgSearchOnlineMail::nsMsgSearchOnlineMail (nsMsgSearchScopeTerm *scope, nsISupportsArray *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());
+ 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,
+ nsISupportsArray *searchTerms,
+ const char16_t *destCharset)
+{
+ 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
+ {
+ uint32_t termCount;
+ searchTerms->Count(&termCount);
+ uint32_t i = 0;
+
+ for (i = 0; i < termCount && asciiOnly; i++)
+ {
+ nsCOMPtr<nsIMsgSearchTerm> pTerm;
+ searchTerms->QueryElementAt(i, NS_GET_IID(nsIMsgSearchTerm),
+ (void **)getter_AddRefs(pTerm));
+
+ 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 = NS_IsAscii(pchar.get());
+ }
+ }
+ }
+// else
+// asciiOnly = false; // TODO: enable this line when the condition is not a plain "true" in the if().
+
+ nsAutoString usAsciiCharSet(NS_LITERAL_STRING("us-ascii"));
+ // Get the optional CHARSET parameter, in case we need it.
+ char *csname = GetImapCharsetParam(asciiOnly ? usAsciiCharSet.get() : 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 arguemnt.
+ nsresult err = nsMsgSearchAdapter::EncodeImap (getter_Copies(imapTerms), searchTerms,
+ asciiOnly ? usAsciiCharSet.get(): destCharset,
+ asciiOnly ? usAsciiCharSet.get(): destCharset, false);
+ if (NS_SUCCEEDED(err))
+ {
+ pEncoding.Append("SEARCH");
+ if (csname)
+ pEncoding.Append(csname);
+ pEncoding.Append(imapTerms);
+ }
+ PR_FREEIF(csname);
+ return err;
+}
+
+
+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;
+}
diff --git a/mailnews/base/search/src/nsMsgLocalSearch.cpp b/mailnews/base/search/src/nsMsgLocalSearch.cpp
new file mode 100644
index 000000000..3ce510b6d
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgLocalSearch.cpp
@@ -0,0 +1,1022 @@
+/* -*- 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/. */
+
+// 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"
+// Disable deprecation warnings generated by nsISupportsArray and associated
+// classes.
+#if defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#elif defined(_MSC_VER)
+#pragma warning (disable : 4996)
+#endif
+#include "nsISupportsArray.h"
+#include "nsMsgBaseCID.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 char *headers,
+ uint32_t headerSize, 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, headerSize, 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, headerSize, 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, headerSize, 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)
+// recurively 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, nsISupportsArray *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,
+ nsISupportsArray *termList,
+ const char *defaultCharset,
+ nsIMsgSearchScopeTerm * scope,
+ nsIMsgDatabase * db,
+ const char * headers,
+ uint32_t headerSize,
+ nsMsgSearchBoolExpression ** aExpressionTree,
+ bool *pResult)
+{
+ return MatchTerms(msgToMatch, termList, defaultCharset, scope, db, headers, headerSize, true, aExpressionTree, pResult);
+}
+
+// static method which matches a header against a list of search terms.
+nsresult
+nsMsgSearchOfflineMail::MatchTermsForSearch(nsIMsgDBHdr *msgToMatch,
+ nsISupportsArray* termList,
+ const char *defaultCharset,
+ nsIMsgSearchScopeTerm *scope,
+ nsIMsgDatabase *db,
+ nsMsgSearchBoolExpression ** aExpressionTree,
+ bool *pResult)
+{
+
+ return MatchTerms(msgToMatch, termList, defaultCharset, scope, db, nullptr, 0, false, aExpressionTree, pResult);
+}
+
+nsresult nsMsgSearchOfflineMail::ConstructExpressionTree(nsISupportsArray * termList,
+ uint32_t termCount,
+ uint32_t &aStartPosInList,
+ nsMsgSearchBoolExpression ** aExpressionTree)
+{
+ nsMsgSearchBoolExpression * finalExpression = *aExpressionTree;
+
+ if (!finalExpression)
+ finalExpression = new nsMsgSearchBoolExpression();
+
+ while (aStartPosInList < termCount)
+ {
+ nsCOMPtr<nsIMsgSearchTerm> pTerm;
+ termList->QueryElementAt(aStartPosInList, NS_GET_IID(nsIMsgSearchTerm), (void **)getter_AddRefs(pTerm));
+ 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 char * headers,
+ uint32_t headerSize,
+ 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(getter_Copies(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.Assign("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::Label:
+ {
+ nsMsgLabelValue label;
+ msgToMatch->GetLabel(&label);
+ err = aTerm->MatchLabel(label, &result);
+ break;
+ }
+ case nsMsgSearchAttrib::Keywords:
+ {
+ nsCString keywords;
+ nsMsgLabelValue label;
+ msgToMatch->GetStringProperty("keywords", getter_Copies(keywords));
+ msgToMatch->GetLabel(&label);
+ if (label >= 1)
+ {
+ if (!keywords.IsEmpty())
+ keywords.Append(' ');
+ keywords.Append("$label");
+ keywords.Append(label + '0');
+ }
+ err = aTerm->MatchKeyword(keywords, &result);
+ break;
+ }
+ case nsMsgSearchAttrib::JunkStatus:
+ {
+ nsCString junkScoreStr;
+ msgToMatch->GetStringProperty("junkscore", getter_Copies(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", getter_Copies(junkScoreOriginStr));
+ msgToMatch->GetStringProperty("junkpercent", getter_Copies(junkPercentStr));
+ if ( junkScoreOriginStr.EqualsLiteral("plugin") &&
+ !junkPercentStr.IsEmpty())
+ {
+ junkPercent = junkPercentStr.ToInteger(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ else
+ {
+ nsCString junkScoreStr;
+ msgToMatch->GetStringProperty("junkscore", getter_Copies(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", getter_Copies(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, headerSize, Filtering,
+ &result);
+ }
+ else {
+ err = NS_ERROR_INVALID_ARG; // ### was SearchError_InvalidAttribute
+ result = false;
+ }
+ }
+
+ *pResult = result;
+ return err;
+}
+
+nsresult nsMsgSearchOfflineMail::MatchTerms(nsIMsgDBHdr *msgToMatch,
+ nsISupportsArray * termList,
+ const char *defaultCharset,
+ nsIMsgSearchScopeTerm * scope,
+ nsIMsgDatabase * db,
+ const char * headers,
+ uint32_t headerSize,
+ bool Filtering,
+ nsMsgSearchBoolExpression ** aExpressionTree,
+ bool *pResult)
+{
+ NS_ENSURE_ARG(aExpressionTree);
+ nsresult err;
+
+ if (!*aExpressionTree)
+ {
+ uint32_t initialPos = 0;
+ uint32_t count;
+ termList->Count(&count);
+ 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, headerSize, Filtering)
+ :true; // vacuously true...
+
+ return NS_OK;
+}
+
+nsresult nsMsgSearchOfflineMail::Search(bool *aDone)
+{
+ nsresult err = NS_OK;
+
+ NS_ENSURE_ARG(aDone);
+ nsresult dbErr = NS_OK;
+ nsCOMPtr<nsIMsgDBHdr> msgDBHdr;
+ 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<nsISupports> currentItem;
+
+ dbErr = m_listContext->GetNext(getter_AddRefs(currentItem));
+ if(NS_SUCCEEDED(dbErr))
+ {
+ msgDBHdr = do_QueryInterface(currentItem, &dbErr);
+ }
+ 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, nsISupportsArray *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
+
+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;
+
+}
+
+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/mailnews/base/search/src/nsMsgLocalSearch.h b/mailnews/base/search/src/nsMsgLocalSearch.h
new file mode 100644
index 000000000..020d7b579
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgLocalSearch.h
@@ -0,0 +1,104 @@
+/* -*- 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"
+#include "nsISimpleEnumerator.h"
+
+
+class nsIMsgDBHdr;
+class nsIMsgSearchScopeTerm;
+class nsIMsgFolder;
+class nsMsgSearchBoolExpression;
+
+class nsMsgSearchOfflineMail : public nsMsgSearchAdapter, public nsIUrlListener
+{
+public:
+ nsMsgSearchOfflineMail (nsIMsgSearchScopeTerm*, nsISupportsArray *);
+
+ 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,
+ nsISupportsArray *termList,
+ const char *defaultCharset,
+ nsIMsgSearchScopeTerm *scope,
+ nsIMsgDatabase * db,
+ const char * headers,
+ uint32_t headerSize,
+ nsMsgSearchBoolExpression ** aExpressionTree,
+ bool *pResult);
+
+ static nsresult MatchTermsForSearch(nsIMsgDBHdr * msgTomatch,
+ nsISupportsArray * 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 char * headers,
+ uint32_t headerSize,
+ bool Filtering,
+ bool *pResult);
+protected:
+ virtual ~nsMsgSearchOfflineMail();
+ static nsresult MatchTerms(nsIMsgDBHdr *msgToMatch,
+ nsISupportsArray *termList,
+ const char *defaultCharset,
+ nsIMsgSearchScopeTerm *scope,
+ nsIMsgDatabase * db,
+ const char * headers,
+ uint32_t headerSize,
+ bool ForFilters,
+ nsMsgSearchBoolExpression ** aExpressionTree,
+ bool *pResult);
+
+ static nsresult ConstructExpressionTree(nsISupportsArray * termList,
+ uint32_t termCount,
+ uint32_t &aStartPosInList,
+ nsMsgSearchBoolExpression ** aExpressionTree);
+
+ nsCOMPtr <nsIMsgDatabase> m_db;
+ nsCOMPtr<nsISimpleEnumerator> m_listContext;
+ void CleanUpScope();
+};
+
+
+class nsMsgSearchOfflineNews : public nsMsgSearchOfflineMail
+{
+public:
+ nsMsgSearchOfflineNews (nsIMsgSearchScopeTerm*, nsISupportsArray *);
+ virtual ~nsMsgSearchOfflineNews ();
+ NS_IMETHOD ValidateTerms () override;
+
+ virtual nsresult OpenSummaryFile () override;
+};
+
+
+
+#endif
+
diff --git a/mailnews/base/search/src/nsMsgSearchAdapter.cpp b/mailnews/base/search/src/nsMsgSearchAdapter.cpp
new file mode 100644
index 000000000..a6f877830
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgSearchAdapter.cpp
@@ -0,0 +1,1332 @@
+/* -*- 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 "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"
+// Disable deprecation warnings generated by nsISupportsArray and associated
+// classes.
+#if defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#elif defined(_MSC_VER)
+#pragma warning (disable : 4996)
+#endif
+#include "nsISupportsArray.h"
+#include "nsAlgorithm.h"
+#include <algorithm>
+#include "mozilla/Attributes.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, nsISupportsArray *searchTerms)
+ : m_searchTerms(searchTerms)
+{
+ m_scope = scope;
+}
+
+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_strdup(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_strdup(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_strdup(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;
+
+ if (m_defaultCharset.IsEmpty())
+ {
+ m_forceAsciiSearch = false; // set the default value in case of error
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIPrefLocalizedString> localizedstr;
+ rv = prefs->GetComplexValue("mailnews.view_default_charset", NS_GET_IID(nsIPrefLocalizedString),
+ getter_AddRefs(localizedstr));
+ if (NS_SUCCEEDED(rv))
+ localizedstr->GetData(getter_Copies(m_defaultCharset));
+
+ prefs->GetBoolPref("mailnews.force_ascii_search", &m_forceAsciiSearch);
+ }
+ }
+ srcCharset = m_defaultCharset.IsEmpty() ?
+ static_cast<const nsAString&>(NS_LITERAL_STRING("ISO-8859-1")) :
+ m_defaultCharset;
+
+ if (m_scope)
+ {
+ // ### DMB is there a way to get the charset for the "window"?
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = m_scope->GetFolder(getter_AddRefs(folder));
+
+ // Ask the newsgroup/folder for its csid.
+ if (NS_SUCCEEDED(rv) && folder)
+ {
+ nsCString folderCharset;
+ folder->GetCharset(folderCharset);
+ dstCharset.Append(NS_ConvertASCIItoUTF16(folderCharset));
+ }
+ }
+ else
+ dstCharset.Assign(srcCharset);
+
+ // If
+ // the destination is still CS_DEFAULT, make the destination match
+ // the source. (CS_DEFAULT is an indication that the charset
+ // was undefined or unavailable.)
+ // ### well, it's not really anymore. Is there an equivalent?
+ if (dstCharset.Equals(m_defaultCharset))
+ {
+ dstCharset.Assign(srcCharset);
+ }
+
+ if (m_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:
+ MOZ_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 = ConvertFromUnicode(NS_LossyConvertUTF16toASCII(destCharset).get(),
+ 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("");
+ NS_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)
+ NS_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, nsISupportsArray *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;
+
+ uint32_t termCount;
+ searchTerms->Count(&termCount);
+ uint32_t i = 0;
+
+ // create our expression
+ nsMsgSearchBoolExpression * expression = new nsMsgSearchBoolExpression();
+ if (!expression)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ for (i = 0; i < termCount && NS_SUCCEEDED(err); i++)
+ {
+ char *termEncoding;
+ bool matchAll;
+ nsCOMPtr<nsIMsgSearchTerm> pTerm;
+ searchTerms->QueryElementAt(i, NS_GET_IID(nsIMsgSearchTerm),
+ (void **)getter_AddRefs(pTerm));
+ pTerm->GetMatchAll(&matchAll);
+ if (matchAll)
+ continue;
+ err = EncodeImapTerm (pTerm, reallyDredd, srcCharset, destCharset, &termEncoding);
+ if (NS_SUCCEEDED(err) && nullptr != termEncoding)
+ {
+ expression = nsMsgSearchBoolExpression::AddSearchTerm(expression, pTerm, termEncoding);
+ delete [] termEncoding;
+ }
+ }
+
+ 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::ValidateTerms (nsISupportsArray *searchTerms)
+{
+ nsresult err = NS_OK;
+ uint32_t count;
+
+ NS_ENSURE_ARG(searchTerms);
+
+ searchTerms->Count(&count);
+ for (uint32_t i = 0; i < count; i++)
+ {
+ nsCOMPtr<nsIMsgSearchTerm> pTerm;
+ searchTerms->QueryElementAt(i, NS_GET_IID(nsIMsgSearchTerm),
+ (void **)getter_AddRefs(pTerm));
+
+ nsIMsgSearchTerm *iTerm = pTerm;
+ nsMsgSearchTerm *term = static_cast<nsMsgSearchTerm *>(iTerm);
+// XP_ASSERT(term->IsValid());
+ bool enabled;
+ bool available;
+ GetEnabled(term->m_attribute, term->m_operator, &enabled);
+ GetAvailable(term->m_attribute, term->m_operator, &available);
+ if (!enabled || !available)
+ {
+ bool validNotShown;
+ GetValidButNotShown(term->m_attribute, term->m_operator,
+ &validNotShown);
+ if (!validNotShown)
+ err = NS_MSG_ERROR_INVALID_SEARCH_SCOPE;
+ }
+ }
+
+ return err;
+}
+
+nsresult
+nsMsgSearchValidityTable::GetAvailableAttributes(uint32_t *length,
+ nsMsgSearchAttribValue **aResult)
+{
+ NS_ENSURE_ARG_POINTER(length);
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ // count first
+ uint32_t totalAttributes=0;
+ int32_t i, j;
+ for (i = 0; i< nsMsgSearchAttrib::kNumMsgSearchAttributes; i++) {
+ for (j=0; j< nsMsgSearchOp::kNumMsgSearchOperators; j++) {
+ if (m_table[i][j].bitAvailable) {
+ totalAttributes++;
+ break;
+ }
+ }
+ }
+
+ nsMsgSearchAttribValue *array = (nsMsgSearchAttribValue*)
+ moz_xmalloc(sizeof(nsMsgSearchAttribValue) * totalAttributes);
+ NS_ENSURE_TRUE(array, NS_ERROR_OUT_OF_MEMORY);
+
+ uint32_t numStored=0;
+ for (i = 0; i< nsMsgSearchAttrib::kNumMsgSearchAttributes; i++) {
+ for (j=0; j< nsMsgSearchOp::kNumMsgSearchOperators; j++) {
+ if (m_table[i][j].bitAvailable) {
+ array[numStored++] = i;
+ break;
+ }
+ }
+ }
+
+ NS_ASSERTION(totalAttributes == numStored, "Search Attributes not lining up");
+ *length = totalAttributes;
+ *aResult = array;
+
+ return NS_OK;
+}
+
+nsresult
+nsMsgSearchValidityTable::GetAvailableOperators(nsMsgSearchAttribValue aAttribute,
+ uint32_t *aLength,
+ nsMsgSearchOpValue **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aLength);
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsMsgSearchAttribValue attr;
+ if (aAttribute == nsMsgSearchAttrib::Default)
+ attr = m_defaultAttrib;
+ else
+ attr = std::min(aAttribute,
+ (nsMsgSearchAttribValue)nsMsgSearchAttrib::OtherHeader);
+
+ uint32_t totalOperators=0;
+ int32_t i;
+ for (i=0; i<nsMsgSearchOp::kNumMsgSearchOperators; i++) {
+ if (m_table[attr][i].bitAvailable)
+ totalOperators++;
+ }
+
+ nsMsgSearchOpValue *array = (nsMsgSearchOpValue*)
+ moz_xmalloc(sizeof(nsMsgSearchOpValue) * totalOperators);
+ NS_ENSURE_TRUE(array, NS_ERROR_OUT_OF_MEMORY);
+
+ uint32_t numStored = 0;
+ for (i=0; i<nsMsgSearchOp::kNumMsgSearchOperators;i++) {
+ if (m_table[attr][i].bitAvailable)
+ array[numStored++] = i;
+ }
+
+ NS_ASSERTION(totalOperators == numStored, "Search Operators not lining up");
+ *aLength = totalOperators;
+ *aResult = array;
+ 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, getter_Copies(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);
+ 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::Label, "Label"},
+ {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);
+ *aTable = new nsMsgSearchValidityTable;
+ if (!*aTable)
+ return NS_ERROR_OUT_OF_MEMORY;
+ NS_ADDREF(*aTable);
+ 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++)
+ {
+ 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);
+ }
+ //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)
+{
+ 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);
+ 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/mailnews/base/search/src/nsMsgSearchImap.h b/mailnews/base/search/src/nsMsgSearchImap.h
new file mode 100644
index 000000000..8f2849da6
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgSearchImap.h
@@ -0,0 +1,37 @@
+/* -*- 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, nsISupportsArray *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,
+ nsISupportsArray *searchTerms,
+ const char16_t *destCharset);
+
+
+protected:
+ nsCString m_encoding;
+};
+
+
+
+#endif
+
diff --git a/mailnews/base/search/src/nsMsgSearchNews.cpp b/mailnews/base/search/src/nsMsgSearchNews.cpp
new file mode 100644
index 000000000..72d2bd648
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgSearchNews.cpp
@@ -0,0 +1,511 @@
+/* -*- 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 "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>
+// Disable deprecation warnings generated by nsISupportsArray and associated
+// classes.
+#if defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#elif defined(_MSC_VER)
+#pragma warning (disable : 4996)
+#endif
+#include "nsISupportsArray.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, nsISupportsArray *termList) : nsMsgSearchAdapter (scope, termList)
+{
+}
+
+
+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->Count(&numTerms);
+ char **intermediateEncodings = new char * [numTerms];
+ if (intermediateEncodings)
+ {
+ // Build an XPAT command for each term
+ int encodingLength = 0;
+ uint32_t i;
+ for (i = 0; i < numTerms; i++)
+ {
+ nsCOMPtr<nsIMsgSearchTerm> pTerm;
+ m_searchTerms->QueryElementAt(i, NS_GET_IID(nsIMsgSearchTerm),
+ (void **)getter_AddRefs(pTerm));
+ // 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_ORSearch = !isBooleanOpAnd;
+
+ 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");
+
+ m_searchTerms->Count(&numTerms);
+
+ for (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;
+ if (!m_ORSearch)
+ {
+ // 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.
+ m_searchTerms->Count(&termCount);
+ }
+ 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))
+ {
+ 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);
+ }
+
+ 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))
+ {
+ 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);
+ }
+
+ return rv;
+}
diff --git a/mailnews/base/search/src/nsMsgSearchNews.h b/mailnews/base/search/src/nsMsgSearchNews.h
new file mode 100644
index 000000000..5c5c1ec31
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgSearchNews.h
@@ -0,0 +1,49 @@
+/* -*- 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"
+
+//-----------------------------------------------------------------------------
+//---------- Adapter class for searching online (news) folders ----------------
+//-----------------------------------------------------------------------------
+
+class nsMsgSearchNews : public nsMsgSearchAdapter
+{
+public:
+ nsMsgSearchNews (nsMsgSearchScopeTerm *scope, nsISupportsArray *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;
+ bool m_ORSearch; // set to true if any of the search terms contains an OR for a boolean operator.
+
+ 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/mailnews/base/search/src/nsMsgSearchSession.cpp b/mailnews/base/search/src/nsMsgSearchSession.cpp
new file mode 100644
index 000000000..422e17c40
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgSearchSession.cpp
@@ -0,0 +1,675 @@
+/* -*- 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 "nsMsgBaseCID.h"
+#include "nsMsgFolderFlags.h"
+#include "nsMsgLocalSearch.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsAutoPtr.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;
+ nsresult rv = NS_NewISupportsArray(getter_AddRefs(m_termList));
+ if (NS_FAILED(rv))
+ NS_ASSERTION(false, "Failed to allocate a nsISupportsArray for nsMsgFilter");
+}
+
+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);
+ NS_ENSURE_TRUE(m_termList, NS_ERROR_NOT_INITIALIZED);
+ delete m_expressionTree;
+ m_expressionTree = nullptr;
+ return m_termList->AppendElement(aTerm);
+}
+
+NS_IMETHODIMP
+nsMsgSearchSession::GetSearchTerms(nsISupportsArray **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = m_termList;
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchSession::CreateTerm(nsIMsgSearchTerm **aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+ nsMsgSearchTerm *term = new nsMsgSearchTerm;
+ NS_ENSURE_TRUE(term, NS_ERROR_OUT_OF_MEMORY);
+
+ *aResult = static_cast<nsIMsgSearchTerm*>(term);
+ NS_ADDREF(*aResult);
+ 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);
+ return m_termList->Count(aNumSearchTerms);
+}
+
+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;
+ *folder = scopeTerm->m_folder;
+ NS_IF_ADDREF(*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::GetSearchParam(void **aSearchParam)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgSearchSession::GetSearchType(nsMsgSearchType **aSearchType)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgSearchSession::SetSearchParam(nsMsgSearchType *type,
+ void *param,
+ nsMsgSearchType **_retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+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.swap(*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->Count(&numTerms);
+ // 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))
+ {
+ nsCOMPtr<nsIMsgSearchAdapter> adapter = do_QueryInterface(scope->m_adapter);
+ if (adapter)
+ {
+ m_runningUrl.Truncate();
+ 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.get());
+ 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->InitWithFuncCallback(TimerCallback, (void *) this, 0,
+ nsITimer::TYPE_REPEATING_SLACK);
+ 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(NS_MSGMAILSESSION_CONTRACTID);
+ 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, false);
+ }
+}
+
+//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/mailnews/base/search/src/nsMsgSearchSession.h b/mailnews/base/search/src/nsMsgSearchSession.h
new file mode 100644
index 000000000..d5d62654f
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgSearchSession.h
@@ -0,0 +1,98 @@
+/* -*- 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 "nsIMsgWindow.h"
+#include "nsITimer.h"
+// Disable deprecation warnings generated by nsISupportsArray and associated
+// classes.
+#if defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#elif defined(_MSC_VER)
+#pragma warning (disable : 4996)
+#endif
+#include "nsISupportsArray.h"
+#include "nsCOMArray.h"
+#include "nsWeakReference.h"
+#include "nsTObserverArray.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;
+ nsCOMPtr <nsISupportsArray> 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;
+ nsMsgSearchType m_searchType;
+ 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/mailnews/base/search/src/nsMsgSearchTerm.cpp b/mailnews/base/search/src/nsMsgSearchTerm.cpp
new file mode 100644
index 000000000..139ab7b20
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgSearchTerm.cpp
@@ -0,0 +1,2088 @@
+/* -*- 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 "nsMsgMimeCID.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 "nsMsgBaseCID.h"
+#include "nsIMsgTagService.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMsgFilterService.h"
+#include "nsIMsgPluggableStore.h"
+#include "nsAbBaseCID.h"
+#include "nsIAbManager.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/mailnews/MimeHeaderParser.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::Label, "label"},
+ {nsMsgSearchAttrib::Keywords, "tag"},
+ {nsMsgSearchAttrib::Size, "size"},
+ // this used to be nsMsgSearchAttrib::SenderInAddressBook
+ // we used to have two Sender menuitems
+ // for backward compatability, 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, getter_Copies(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.string=nullptr;
+ 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;
+}
+
+
+
+nsMsgSearchTerm::~nsMsgSearchTerm ()
+{
+ if (IS_STRING_ATTRIBUTE (m_attribute) && m_value.string)
+ NS_Free(m_value.string);
+}
+
+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.string)
+ {
+ bool quoteVal = false;
+ // need to quote strings with ')' and strings starting with '"' or ' '
+ // filter code will escape quotes
+ if (PL_strchr(m_value.string, ')') ||
+ (m_value.string[0] == ' ') ||
+ (m_value.string[0] == '"'))
+ {
+ quoteVal = true;
+ outputStr += "\"";
+ }
+ if (PL_strchr(m_value.string, '"'))
+ {
+ char *escapedString = nsMsgSearchTerm::EscapeQuotesInStr(m_value.string);
+ if (escapedString)
+ {
+ outputStr += escapedString;
+ PR_Free(escapedString);
+ }
+
+ }
+ else
+ {
+ outputStr += m_value.string;
+ }
+ 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::Label:
+ {
+ outputStr.AppendInt(m_value.u.label);
+ 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.Append("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.string = (char *) PR_Malloc(valueLen + 1);
+ PL_strncpy(m_value.string, inStream, valueLen + 1);
+ m_value.string[valueLen] = '\0';
+ CopyUTF8toUTF16(m_value.string, 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::Label:
+ m_value.u.label = 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 err = NS_MsgGetOperatorFromString(inStream, &operatorVal);
+ *value = (nsMsgSearchOpValue) operatorVal;
+ return err;
+}
+
+// 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) // if 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);
+ if (m_attribute == nsMsgSearchAttrib::Label)
+ {
+ nsAutoCString keyword("$label");
+ m_value.attribute = m_attribute = nsMsgSearchAttrib::Keywords;
+ keyword.Append('0' + m_value.u.label);
+ m_value.string = PL_strdup(keyword.get());
+ CopyUTF8toUTF16(m_value.string, m_value.utf16String);
+ }
+ 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 char * headers,
+ uint32_t headersSize,
+ 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;
+ // init result to what we want if we don't find the header at all
+ bool result = !matchExpected;
+
+ nsCString dbHdrValue;
+ msg->GetStringProperty(m_arbitraryHeader.get(), getter_Copies(dbHdrValue));
+ if (!dbHdrValue.IsEmpty())
+ // match value with the other info.
+ return MatchRfc2047String(dbHdrValue, charset, charsetOverride, pResult);
+
+ nsMsgBodyHandler * bodyHandler =
+ new nsMsgBodyHandler (scope, length, msg, db, headers, headersSize,
+ ForFiltering);
+ NS_ENSURE_TRUE(bodyHandler, NS_ERROR_OUT_OF_MEMORY);
+
+ bodyHandler->SetStripHeaders (false);
+
+ nsCString headerFullValue; // contains matched header value accumulated over multiple lines.
+ nsAutoCString buf;
+ nsAutoCString curMsgHeader;
+ bool searchingHeaders = true;
+
+ // We will allow accumulation of received headers;
+ bool isReceivedHeader = m_arbitraryHeader.EqualsLiteral("received");
+
+ while (searchingHeaders)
+ {
+ nsCString charsetIgnored;
+ if (bodyHandler->GetNextLine(buf, charsetIgnored) < 0 || EMPTY_MESSAGE_LINE(buf))
+ searchingHeaders = false;
+ bool isContinuationHeader = searchingHeaders ? NS_IsAsciiWhitespace(buf.CharAt(0))
+ : false;
+
+ // We try to match the header from the last time through the loop, which should now
+ // have accumulated over possible multiple lines. For all headers except received,
+ // we process a single accumulation, but process accumulated received at the end.
+ if (!searchingHeaders || (!isContinuationHeader &&
+ (!headerFullValue.IsEmpty() && !isReceivedHeader)))
+ {
+ // Make sure buf has info besides just the header.
+ // Otherwise, it's either an empty header, or header not found.
+ if (!headerFullValue.IsEmpty())
+ {
+ bool stringMatches;
+ // match value with the other info.
+ rv = MatchRfc2047String(headerFullValue, charset, charsetOverride, &stringMatches);
+ if (matchExpected == stringMatches) // if we found a match
+ {
+ searchingHeaders = false; // then stop examining the headers
+ result = stringMatches;
+ }
+ }
+ 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++; // advance to next character
+
+ // strip trailing white space
+ char * end = buf_end - 1;
+ while (end > headerValue && isspace(*end)) // while we haven't gone back past the start and we are white space....
+ {
+ *end = '\0'; // eat up the white space
+ end--; // move back and examine the previous character....
+ }
+
+ // any continuation whitespace is converted to a single space. This includes both a continuation line, or a
+ // second value of the same header (eg the received header)
+ if (!headerFullValue.IsEmpty())
+ headerFullValue.AppendLiteral(" ");
+ 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(), getter_Copies(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 = PL_strlen (m_value.string);
+
+ 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
+ uint32_t lines = 0;
+
+ // 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;
+
+ // If there's a '=' in the search term, then we're not going to do
+ // quoted printable decoding. Otherwise we assume everything is
+ // quoted printable. Obviously everything isn't quoted printable, but
+ // since we don't have a MIME parser handy, and we want to err on the
+ // side of too many hits rather than not enough, we'll assume in that
+ // general direction. Blech. ### FIX ME
+ // bug fix #314637: for stateful charsets like ISO-2022-JP, we don't
+ // want to decode quoted printable since it contains '='.
+ bool isQuotedPrintable = !nsMsgI18Nstateful_charset(folderCharset) &&
+ (PL_strchr (m_value.string, '=') == nullptr);
+
+ 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 (isQuotedPrintable)
+ {
+ softLineBreak = StringEndsWith(buf, NS_LITERAL_CSTRING("="));
+ MsgStripQuotedPrintable ((unsigned char*)buf.get());
+ // in case the string shrunk, reset the length. If soft line break,
+ // chop off the last char as well.
+ size_t bufLength = strlen(buf.get());
+ if ((bufLength > 0) && softLineBreak)
+ --bufLength;
+ buf.SetLength(bufLength);
+ }
+ 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);
+ lines++;
+ }
+ compare.Truncate();
+ }
+ }
+ else
+ endOfFile = true;
+ }
+#ifdef DO_I18N
+ if(conv)
+ INTL_DestroyCharCodeConverter(conv);
+#endif
+ 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.string))
+ // clear out the directory....we are no longer pointing to the right one
+ mDirectory = nullptr;
+ }
+ if (!mDirectory)
+ {
+ nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = abManager->GetDirectory(nsDependentCString(m_value.string), 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);
+
+ nsCOMPtr<nsIMimeConverter> mimeConverter = do_GetService(NS_MIME_CONVERTER_CONTRACTID);
+ nsAutoString stringToMatch;
+ nsresult 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;
+ if (charset != nullptr)
+ {
+ ConvertToUnicode(charset, nsCString(stringToMatch), utf16StrToMatch);
+ }
+ else {
+ NS_ASSERTION(MsgIsUTF8(stringToMatch), "stringToMatch is not UTF-8");
+ CopyUTF8toUTF16(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 && !strcmp(aJunkScoreOrigin, m_value.string);
+ break;
+ case nsMsgSearchOp::Isnt:
+ matches = !aJunkScoreOrigin || strcmp(aJunkScoreOrigin, m_value.string);
+ 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;
+}
+
+
+nsresult nsMsgSearchTerm::MatchLabel(nsMsgLabelValue aLabelValue, bool *pResult)
+{
+ NS_ENSURE_ARG_POINTER(pResult);
+
+ nsresult rv = NS_OK;
+ bool result = false;
+ switch (m_operator)
+ {
+ case nsMsgSearchOp::Is:
+ if (m_value.u.label == aLabelValue)
+ result = true;
+ break;
+ case nsMsgSearchOp::Isnt:
+ if (m_value.u.label != aLabelValue)
+ result = true;
+ break;
+ default:
+ rv = NS_ERROR_FAILURE;
+ NS_ERROR("invalid compare op for label value");
+ }
+
+ *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 = PL_strlen(m_value.string);
+ const char* matchStart = PL_strstr(keywordString.get(), m_value.string);
+ 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.string);
+ }
+ // 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(NS_MSGTAGSERVICE_CONTRACTID, &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.string);
+
+ // 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(NS_MSGFILTERSERVICE_CONTRACTID, &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, nsDependentCString(m_value.string),
+ 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);
+ *aResult = new nsMsgSearchValueImpl(&m_value);
+ NS_IF_ADDREF(*aResult);
+ 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.Assign(NS_LITERAL_CSTRING("Sender"));
+ else if (aValue.LowerCaseEqualsLiteral("date"))
+ aValue.Assign(NS_LITERAL_CSTRING("Date"));
+ else if (aValue.LowerCaseEqualsLiteral("status"))
+ aValue.Assign(NS_LITERAL_CSTRING("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);
+ NS_IF_ADDREF(*aResult = searchSession);
+ 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);
+ bool reusable;
+ nsresult rv = m_folder->GetMsgInputStream(aMsgHdr, &reusable,
+ 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 (nsISupportsArray *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::Label:
+ rv = src->GetLabel(&dst->u.label);
+ 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);
+ dst->string = ToNewUTF8String(unicodeString);
+ 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/mailnews/base/search/src/nsMsgSearchValue.cpp b/mailnews/base/search/src/nsMsgSearchValue.cpp
new file mode 100644
index 000000000..7685c4a73
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgSearchValue.cpp
@@ -0,0 +1,117 @@
+/* -*- 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 "MailNewsTypes.h"
+#include "nsMsgSearchValue.h"
+#include "nsIMsgFolder.h"
+#include "nsMsgUtils.h"
+#include "nsStringGlue.h"
+
+nsMsgSearchValueImpl::nsMsgSearchValueImpl(nsMsgSearchValue *aInitialValue)
+{
+ mValue = *aInitialValue;
+ if (IS_STRING_ATTRIBUTE(aInitialValue->attribute) && aInitialValue->string)
+ {
+ mValue.string = NS_strdup(aInitialValue->string);
+ CopyUTF8toUTF16(mValue.string, mValue.utf16String);
+ }
+ else
+ mValue.string = 0;
+}
+
+nsMsgSearchValueImpl::~nsMsgSearchValueImpl()
+{
+ if (IS_STRING_ATTRIBUTE(mValue.attribute))
+ NS_Free(mValue.string);
+}
+
+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, Label, nsMsgLabelValue, mValue.u.label)
+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);
+ *aResult = mValue.u.folder;
+ NS_IF_ADDREF(*aResult);
+ 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);
+ if (mValue.string)
+ NS_Free(mValue.string);
+ mValue.string = ToNewUTF8String(aValue);
+ 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::Label:
+ 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.AppendLiteral("]");
+
+ return NS_OK;
+}
diff --git a/mailnews/base/search/src/nsMsgSearchValue.h b/mailnews/base/search/src/nsMsgSearchValue.h
new file mode 100644
index 000000000..ef50ad1ed
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgSearchValue.h
@@ -0,0 +1,26 @@
+/* -*- 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:
+ nsMsgSearchValueImpl(nsMsgSearchValue *aInitialValue);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGSEARCHVALUE
+
+ private:
+ virtual ~nsMsgSearchValueImpl();
+
+ nsMsgSearchValue mValue;
+
+};
+
+#endif
diff --git a/mailnews/base/search/src/nsMsgTraitService.js b/mailnews/base/search/src/nsMsgTraitService.js
new file mode 100644
index 000000000..eda20bbf8
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgTraitService.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/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+// 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;
+ return;
+}
+
+function nsMsgTraitService() {}
+
+nsMsgTraitService.prototype =
+{
+ // Component setup
+ classID: Components.ID("{A2E95F4F-DA72-4a41-9493-661AD353C00A}"),
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Components.interfaces.nsIMsgTraitService]),
+
+ // nsIMsgTraitService implementation
+
+ get lastIndex()
+ {
+ return _lastIndex;
+ },
+
+ registerTrait: function(aId)
+ {
+ if (_traits[aId])
+ return 0; // meaning already registered
+ _registerTrait(aId, ++_lastIndex);
+ traitsBranch.setBoolPref("enabled." + _lastIndex, false);
+ traitsBranch.setCharPref("id." + _lastIndex, aId);
+ return _lastIndex;
+ },
+
+ unRegisterTrait: function(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);
+ }
+ return;
+ },
+
+ isRegistered: function(aId)
+ {
+ return _traits[aId] ? true : false;
+ },
+
+ setName: function(aId, aName)
+ {
+ traitsBranch.setCharPref("name." + _traits[aId].index, aName);
+ _traits[aId].name = aName;
+ },
+
+ getName: function(aId)
+ {
+ return _traits[aId].name;
+ },
+
+ getIndex: function(aId)
+ {
+ return _traits[aId].index;
+ },
+
+ getId: function(aIndex)
+ {
+ for (let id in _traits)
+ if (_traits[id].index == aIndex)
+ return id;
+ return null;
+ },
+
+ setEnabled: function(aId, aEnabled)
+ {
+ traitsBranch.setBoolPref("enabled." + _traits[aId].index, aEnabled);
+ _traits[aId].enabled = aEnabled;
+ },
+
+ getEnabled: function(aId)
+ {
+ return _traits[aId].enabled;
+ },
+
+ setAntiId: function(aId, aAntiId)
+ {
+ traitsBranch.setCharPref("antiId." + _traits[aId].index, aAntiId);
+ _traits[aId].antiId = aAntiId;
+ },
+
+ getAntiId: function(aId)
+ {
+ return _traits[aId].antiId;
+ },
+
+ getEnabledIndices: function(aCount, aProIndices, aAntiIndices)
+ {
+ let proIndices = [];
+ let antiIndices = [];
+ for (let id in _traits)
+ if (_traits[id].enabled)
+ {
+ proIndices.push(_traits[id].index);
+ antiIndices.push(_traits[_traits[id].antiId].index);
+ }
+ aCount.value = proIndices.length;
+ aProIndices.value = proIndices;
+ aAntiIndices.value = antiIndices;
+ return;
+ },
+
+ addAlias: function addAlias(aTraitIndex, aTraitAliasIndex)
+ {
+ let aliasesString = "";
+ try {
+ aliasesString = traitsBranch.getCharPref("aliases." + aTraitIndex);
+ }
+ catch (e) {}
+ let aliases;
+ if (aliasesString.length)
+ aliases = aliasesString.split(",");
+ else
+ aliases = [];
+ if (aliases.indexOf(aTraitAliasIndex.toString()) == -1)
+ {
+ aliases.push(aTraitAliasIndex);
+ traitsBranch.setCharPref("aliases." + aTraitIndex, aliases.join());
+ }
+ },
+
+ removeAlias: function removeAlias(aTraitIndex, aTraitAliasIndex)
+ {
+ let aliasesString = "";
+ try {
+ aliasesString = traitsBranch.getCharPref("aliases." + aTraitIndex);
+ }
+ catch (e) {
+ return;
+ }
+ let aliases;
+ if (aliasesString.length)
+ aliases = aliasesString.split(",");
+ else
+ aliases = [];
+ let location;
+ if ((location = aliases.indexOf(aTraitAliasIndex.toString())) != -1)
+ {
+ aliases.splice(location, 1);
+ traitsBranch.setCharPref("aliases." + aTraitIndex, aliases.join());
+ }
+ },
+
+ getAliases: function getAliases(aTraitIndex, aLength)
+ {
+ let aliasesString = "";
+ try {
+ aliasesString = traitsBranch.getCharPref("aliases." + aTraitIndex);
+ }
+ catch (e) {}
+
+ let aliases;
+ if (aliasesString.length)
+ aliases = aliasesString.split(",");
+ else
+ aliases = [];
+ aLength.value = aliases.length;
+ 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 (var i = 0; i < ids.length; i++)
+ {
+ var id = idBranch.getCharPref(ids[i]);
+ var index = parseInt(ids[i]);
+ _registerTrait(id, index, false);
+
+ // Read in values, ignore errors since that usually means the
+ // value does not exist
+ try {
+ _traits[id].name = nameBranch.getCharPref(ids[i]);
+ }
+ catch (e) {}
+
+ try {
+ _traits[id].enabled = enabledBranch.getBoolPref(ids[i]);
+ }
+ catch (e) {}
+
+ try {
+ _traits[id].antiId = antiIdBranch.getCharPref(ids[i]);
+ }
+ catch (e) {}
+
+ if (_lastIndex < index)
+ _lastIndex = index;
+ }
+
+ //for (traitId in _traits)
+ // dump("\nindex of " + traitId + " is " + _traits[traitId].index);
+ //dump("\n");
+}
+
+var components = [nsMsgTraitService];
+var NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/mailnews/base/search/src/nsMsgTraitService.manifest b/mailnews/base/search/src/nsMsgTraitService.manifest
new file mode 100644
index 000000000..0a13ee781
--- /dev/null
+++ b/mailnews/base/search/src/nsMsgTraitService.manifest
@@ -0,0 +1,2 @@
+component {A2E95F4F-DA72-4a41-9493-661AD353C00A} nsMsgTraitService.js
+contract @mozilla.org/msg-trait-service;1 {A2E95F4F-DA72-4a41-9493-661AD353C00A}