diff options
Diffstat (limited to 'mailnews/base/search')
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="©Message.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="−" + 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="¬Junk.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} |