Issue #1258 - Part 1: Import mailnews, ldap, and mork from comm-esr52.9.1

This commit is contained in:
Matt A. Tobin 2019-11-03 00:17:46 -04:00 • committed by Roy Tam
commit e400f4130a
1564 changed files with 510348 additions and 0 deletions

View file

@ -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();
}

View file

@ -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>

View file

@ -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();
}

View file

@ -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>

View file

@ -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;
}

View file

@ -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>

View file

@ -0,0 +1,738 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!--
This file has the following external dependencies:
-gFilterActionStrings from FilterEditor.js
-gFilterList from FilterEditor.js
-gFilter from FilterEditor.js
-gCustomActions from FilterEditor.js
-gFilterType from FilterEditor.js
-checkActionsReorder from FilterEditor.js
-->
<!DOCTYPE dialog [
<!ENTITY % filterEditorDTD SYSTEM "chrome://messenger/locale/FilterEditor.dtd" >
%filterEditorDTD;
<!ENTITY % messengerDTD SYSTEM "chrome://messenger/locale/messenger.dtd" >
%messengerDTD;
]>
<bindings id="filterBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:nc="http://home.netscape.com/NC-rdf#"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="ruleactiontype-menulist">
<content>
<xul:menulist class="ruleaction-type">
<xul:menupopup>
<xul:menuitem label="&moveMessage.label;" value="movemessage" enablefornews="false"/>
<xul:menuitem label="&copyMessage.label;" value="copymessage"/>
<xul:menuseparator enablefornews="false"/>
<xul:menuitem label="&forwardTo.label;" value="forwardmessage" enablefornews="false"/>
<xul:menuitem label="&replyWithTemplate.label;" value="replytomessage" enablefornews="false"/>
<xul:menuseparator/>
<xul:menuitem label="&markMessageRead.label;" value="markasread"/>
<xul:menuitem label="&markMessageUnread.label;" value="markasunread"/>
<xul:menuitem label="&markMessageStarred.label;" value="markasflagged"/>
<xul:menuitem label="&setPriority.label;" value="setpriorityto"/>
<xul:menuitem label="&addTag.label;" value="addtagtomessage"/>
<xul:menuitem label="&setJunkScore.label;" value="setjunkscore" enablefornews="false"/>
<xul:menuseparator enableforpop3="true"/>
<xul:menuitem label="&deleteMessage.label;" value="deletemessage"/>
<xul:menuitem label="&deleteFromPOP.label;" value="deletefrompopserver" enableforpop3="true"/>
<xul:menuitem label="&fetchFromPOP.label;" value="fetchfrompopserver" enableforpop3="true"/>
<xul:menuseparator/>
<xul:menuitem label="&ignoreThread.label;" value="ignorethread"/>
<xul:menuitem label="&ignoreSubthread.label;" value="ignoresubthread"/>
<xul:menuitem label="&watchThread.label;" value="watchthread"/>
<xul:menuseparator/>
<xul:menuitem label="&stopExecution.label;" value="stopexecution"/>
</xul:menupopup>
</xul:menulist>
</content>
<implementation>
<constructor>
<![CDATA[
this.addCustomActions();
this.hideInvalidActions();
// differentiate between creating a new, next available action,
// and creating a row which will be initialized with an action
if (!this.parentNode.hasAttribute('initialActionIndex'))
{
var unavailableActions = this.usedActionsList();
// select the first one that's not in the list
for (var index = 0; index < this.menuitems.length; index++)
{
var menu = this.menuitems[index];
if (!(menu.value in unavailableActions) && !menu.hidden)
{
this.menulist.value = menu.value;
this.parentNode.setAttribute('value', menu.value);
break;
}
}
}
else
{
this.parentNode.mActionTypeInitialized = true;
this.parentNode.clearInitialActionIndex();
}
]]>
</constructor>
<field name="menulist">document.getAnonymousNodes(this)[0]</field>
<field name="menuitems">this.menulist.getElementsByTagNameNS(this.menulist.namespaceURI, 'menuitem')</field>
<method name="hideInvalidActions">
<body>
<![CDATA[
let menupopup = this.menulist.menupopup;
let scope = getScopeFromFilterList(gFilterList);
// walk through the list of filter actions and hide any actions which aren't valid
// for our given scope (news, imap, pop, etc) and context
let elements, i;
// disable / enable all elements in the "filteractionlist"
// based on the scope and the "enablefornews" attribute
elements = menupopup.getElementsByAttribute("enablefornews", "true");
for (i = 0; i < elements.length; i++)
elements[i].hidden = scope != Components.interfaces.nsMsgSearchScope.newsFilter;
elements = menupopup.getElementsByAttribute("enablefornews", "false");
for (i = 0; i < elements.length; i++)
elements[i].hidden = scope == Components.interfaces.nsMsgSearchScope.newsFilter;
elements = menupopup.getElementsByAttribute("enableforpop3", "true");
for (i = 0; i < elements.length; i++)
elements[i].hidden = !((gFilterList.folder.server.type == "pop3") ||
(gFilterList.folder.server.type == "none"));
elements = menupopup.getElementsByAttribute("isCustom", "true");
// Note there might be an additional element here as a placeholder
// for a missing action, so we iterate over the known actions
// instead of the elements.
for (i = 0; i < gCustomActions.length; i++)
elements[i].hidden = !gCustomActions[i]
.isValidForType(gFilterType, scope);
// Disable "Reply with Template" if there are no templates.
if (!this.getTemplates(false)) {
elements = menupopup.getElementsByAttribute("value", "replytomessage");
if (elements.length == 1)
elements[0].hidden = true;
}
]]>
</body>
</method>
<method name="addCustomActions">
<body>
<![CDATA[
var menupopup = this.menulist.menupopup;
for (var i = 0; i < gCustomActions.length; i++)
{
var customAction = gCustomActions[i];
var menuitem = document.createElementNS(
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
"xul:menuitem");
menuitem.setAttribute("label", customAction.name);
menuitem.setAttribute("value", customAction.id);
menuitem.setAttribute("isCustom", "true");
menupopup.appendChild(menuitem);
}
]]>
</body>
</method>
<!-- returns a hash containing all of the filter actions which are currently being used by other filteractionrows -->
<method name="usedActionsList">
<body>
<![CDATA[
var usedActions = {};
var currentFilterActionRow = this.parentNode;
var listBox = currentFilterActionRow.mListBox; // need to account for the list item
// now iterate over each list item in the list box
for (var index = 0; index < listBox.getRowCount(); index++)
{
var filterActionRow = listBox.getItemAtIndex(index);
if (filterActionRow != currentFilterActionRow)
{
var actionValue = filterActionRow.getAttribute('value');
// let custom actions decide if dups are allowed
var isCustom = false;
for (var i = 0; i < gCustomActions.length; i++)
{
if (gCustomActions[i].id == actionValue)
{
isCustom = true;
if (!gCustomActions[i].allowDuplicates)
usedActions[actionValue] = true;
break;
}
}
if (!isCustom) {
// The following actions can appear more than once in a single filter
// so do not set them as already used.
if (actionValue != 'addtagtomessage' &&
actionValue != 'forwardmessage' &&
actionValue != 'copymessage')
usedActions[actionValue] = true;
// If either Delete message or Move message exists, disable the other one.
// It does not make sense to apply both to the same message.
if (actionValue == 'deletemessage')
usedActions['movemessage'] = true;
else if (actionValue == 'movemessage')
usedActions['deletemessage'] = true;
// The same with Mark as read/Mark as Unread.
else if (actionValue == 'markasread')
usedActions['markasunread'] = true;
else if (actionValue == 'markasunread')
usedActions['markasread'] = true;
}
}
}
return usedActions;
]]>
</body>
</method>
<!--
- Check if there exist any templates in this account.
-
- @param populateTemplateList If true, create menuitems representing
- the found templates.
- @param templateMenuList The menulist element to create items in.
-
- @return True if at least one template was found, otherwise false.
-->
<method name="getTemplates">
<parameter name="populateTemplateList"/>
<parameter name="templateMenuList"/>
<body>
<![CDATA[
Components.utils.import("resource:///modules/iteratorUtils.jsm", this);
let identitiesRaw = MailServices.accounts
.getIdentitiesForServer(gFilterList.folder.server);
let identities = Array.from(this.fixIterator(identitiesRaw,
Components.interfaces.nsIMsgIdentity));
if (!identities.length) // typically if this is Local Folders
identities.push(MailServices.accounts.defaultAccount.defaultIdentity);
let templateFound = false;
let foldersScanned = [];
for (let identity of identities) {
let enumerator = null;
let msgFolder;
try {
msgFolder = Components.classes["@mozilla.org/rdf/rdf-service;1"]
.getService(Components.interfaces.nsIRDFService)
.GetResource(identity.stationeryFolder)
.QueryInterface(Components.interfaces.nsIMsgFolder);
// If we already processed this folder, do not set enumerator
// so that we skip this identity.
if (foldersScanned.indexOf(msgFolder) == -1) {
foldersScanned.push(msgFolder);
enumerator = msgFolder.msgDatabase.EnumerateMessages();
}
} catch (e) {
// The Templates folder may not exist, that is OK.
}
if (!enumerator)
continue;
while (enumerator.hasMoreElements()) {
let header = enumerator.getNext();
if (header instanceof Components.interfaces.nsIMsgDBHdr) {
templateFound = true;
if (!populateTemplateList)
return true;
let msgTemplateUri = msgFolder.URI + "?messageId=" +
header.messageId + '&subject=' + header.mime2DecodedSubject;
let newItem = templateMenuList.appendItem(header.mime2DecodedSubject,
msgTemplateUri);
}
}
}
return templateFound;
]]>
</body>
</method>
</implementation>
<handlers>
<handler event="command">
<![CDATA[
this.parentNode.setAttribute('value', this.menulist.value);
checkActionsReorder();
]]>
</handler>
<handler event="popupshowing">
<![CDATA[
var unavailableActions = this.usedActionsList();
for (var index = 0; index < this.menuitems.length; index++)
{
var menu = this.menuitems[index];
menu.setAttribute('disabled', menu.value in unavailableActions);
}
]]>
</handler>
</handlers>
</binding>
<!-- This binding exists to disable the default binding of a listitem
in the search terms. -->
<binding id="listitem">
<implementation>
<method name="_fireEvent">
<parameter name="aName"/>
<body>
<![CDATA[
/* This provides a dummy _fireEvent function that
the listbox expects to be able to call.
See bug 202036. */
]]>
</body>
</method>
</implementation>
</binding>
<binding id="ruleaction" extends="#listitem">
<content allowevents="true">
<xul:listcell class="ruleactiontype"
orient="vertical" align="stretch" pack="center"/>
<xul:listcell class="ruleactiontarget" xbl:inherits="type=value"
orient="vertical" align="stretch" pack="center"/>
<xul:listcell>
<xul:button class="small-button"
label="+"
tooltiptext="&addAction.tooltip;"
oncommand="this.parentNode.parentNode.addRow();"/>
<xul:button class="small-button"
label="&#x2212;"
tooltiptext="&removeAction.tooltip;"
oncommand="this.parentNode.parentNode.removeRow();"
anonid="removeButton"/>
</xul:listcell>
</content>
<implementation>
<field name="mListBox">this.parentNode</field>
<field name="mRemoveButton">document.getAnonymousElementByAttribute(this, "anonid", "removeButton")</field>
<field name="mActionTypeInitialized">false</field>
<field name="mRuleActionTargetInitialized">false</field>
<field name="mRuleActionType">document.getAnonymousNodes(this)[0]</field>
<method name="clearInitialActionIndex">
<body>
<![CDATA[
// we should only remove the initialActionIndex after we have been told that
// both the rule action type and the rule action target have both been built since they both need
// this piece of information. This complication arises because both of these child elements are getting
// bound asynchronously after the search row has been constructed
if (this.mActionTypeInitialized && this.mRuleActionTargetInitialized)
this.removeAttribute('initialActionIndex');
]]>
</body>
</method>
<method name="initWithAction">
<parameter name="aFilterAction"/>
<body>
<![CDATA[
var filterActionStr;
var actionTarget = document.getAnonymousNodes(this)[1];
var actionItem = document.getAnonymousNodes(actionTarget);
var nsMsgFilterAction = Components.interfaces.nsMsgFilterAction;
switch (aFilterAction.type)
{
case nsMsgFilterAction.Custom:
filterActionStr = aFilterAction.customId;
if (actionItem)
actionItem[0].value = aFilterAction.strValue;
// Make sure the custom action has been added. If not, it
// probably was from an extension that has been removed. We'll
// show a dummy menuitem to warn the user.
var needCustomLabel = true;
for (var i = 0; i < gCustomActions.length; i++)
{
if (gCustomActions[i].id == filterActionStr)
{
needCustomLabel = false;
break;
}
}
if (needCustomLabel)
{
var menuitem = document.createElementNS(
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
"xul:menuitem");
menuitem.setAttribute("label",
gFilterBundle.getString("filterMissingCustomAction"));
menuitem.setAttribute("value", filterActionStr);
menuitem.disabled = true;
this.mRuleActionType.menulist.menupopup.appendChild(menuitem);
var scriptError = Components.classes["@mozilla.org/scripterror;1"]
.createInstance(Components.interfaces.nsIScriptError);
scriptError.init("Missing custom action " + filterActionStr,
null, null, 0, 0,
Components.interfaces.nsIScriptError.errorFlag,
"component javascript");
Services.console.logMessage(scriptError);
}
break;
case nsMsgFilterAction.MoveToFolder:
case nsMsgFilterAction.CopyToFolder:
actionItem[0].value = aFilterAction.targetFolderUri;
break;
case nsMsgFilterAction.Reply:
case nsMsgFilterAction.Forward:
actionItem[0].value = aFilterAction.strValue;
break;
case nsMsgFilterAction.Label:
actionItem[0].value = aFilterAction.label;
break;
case nsMsgFilterAction.ChangePriority:
actionItem[0].value = aFilterAction.priority;
break;
case nsMsgFilterAction.JunkScore:
actionItem[0].value = aFilterAction.junkScore;
break;
case nsMsgFilterAction.AddTag:
actionItem[0].value = aFilterAction.strValue;
break;
default:
break;
}
if (aFilterAction.type != nsMsgFilterAction.Custom)
filterActionStr = gFilterActionStrings[aFilterAction.type];
document.getAnonymousNodes(this.mRuleActionType)[0]
.value = filterActionStr;
this.mRuleActionTargetInitialized = true;
this.clearInitialActionIndex();
checkActionsReorder();
]]>
</body>
</method>
<method name="validateAction">
<body>
<![CDATA[
// returns true if this row represents a valid filter action and false otherwise.
// This routine also prompts the user.
Components.utils.import("resource:///modules/MailUtils.js", this);
var filterActionString = this.getAttribute('value');
var actionTarget = document.getAnonymousNodes(this)[1];
var errorString, customError;
switch (filterActionString)
{
case "movemessage":
case "copymessage":
let msgFolder = document.getAnonymousNodes(actionTarget)[0].value ?
this.MailUtils.getFolderForURI(document.getAnonymousNodes(actionTarget)[0].value) : null;
if (!msgFolder || !msgFolder.canFileMessages)
errorString = "mustSelectFolder";
break;
case "forwardmessage":
if (document.getAnonymousNodes(actionTarget)[0].value.length < 3 ||
document.getAnonymousNodes(actionTarget)[0].value.indexOf('@') < 1)
errorString = "enterValidEmailAddress";
break;
case "replytomessage":
if (!document.getAnonymousNodes(actionTarget)[0].selectedItem)
errorString = "pickTemplateToReplyWith";
break;
default:
// some custom actions have no action value node
if (!document.getAnonymousNodes(actionTarget))
return true;
// locate the correct custom action, and check validity
for (var i = 0; i < gCustomActions.length; i++)
if (gCustomActions[i].id == filterActionString)
{
customError =
gCustomActions[i].validateActionValue(
document.getAnonymousNodes(actionTarget)[0].value,
gFilterList.folder, gFilterType);
break;
}
break;
}
errorString = errorString ?
gFilterBundle.getString(errorString) :
customError;
if (errorString)
Services.prompt.alert(window, null, errorString);
return !errorString;
]]>
</body>
</method>
<method name="saveToFilter">
<parameter name="aFilter"/>
<body>
<![CDATA[
// create a new filter action, fill it in, and then append it to the filter
var filterAction = aFilter.createAction();
var filterActionString = this.getAttribute('value');
filterAction.type = gFilterActionStrings.indexOf(filterActionString);
var actionTarget = document.getAnonymousNodes(this)[1];
var actionItem = document.getAnonymousNodes(actionTarget);
var nsMsgFilterAction = Components.interfaces.nsMsgFilterAction;
switch (filterAction.type)
{
case nsMsgFilterAction.Label:
filterAction.label = actionItem[0].getAttribute("value");
break;
case nsMsgFilterAction.ChangePriority:
filterAction.priority = actionItem[0].getAttribute("value");
break;
case nsMsgFilterAction.MoveToFolder:
case nsMsgFilterAction.CopyToFolder:
filterAction.targetFolderUri = actionItem[0].value;
break;
case nsMsgFilterAction.JunkScore:
filterAction.junkScore = actionItem[0].value;
break;
case nsMsgFilterAction.Custom:
filterAction.customId = filterActionString;
// fall through to set the value
default:
if (actionItem)
filterAction.strValue = actionItem[0].value;
break;
}
aFilter.appendAction(filterAction);
]]>
</body>
</method>
<method name="getActionStrings">
<parameter name="aActionStrings"/>
<body>
<![CDATA[
// Collect the action names and arguments in a plain string form.
let actionTarget = document.getAnonymousNodes(this)[1];
let actionItem = document.getAnonymousNodes(actionTarget);
aActionStrings.push({
label: document.getAnonymousNodes(this.mRuleActionType)[0].label,
argument: actionItem ?
(actionItem[0].label ?
actionItem[0].label : actionItem[0].value) : ""
});
]]>
</body>
</method>
<method name="updateRemoveButton">
<body>
<![CDATA[
// if we only have one row of actions, then disable the remove button for that row
this.mListBox.getItemAtIndex(0).mRemoveButton.disabled = this.mListBox.getRowCount() == 1;
]]>
</body>
</method>
<method name="addRow">
<body>
<![CDATA[
let listItem = document.createElement('listitem');
listItem.className = 'ruleaction';
listItem.setAttribute('onfocus','this.storeFocus();');
this.mListBox.insertBefore(listItem, this.nextSibling);
this.mListBox.ensureElementIsVisible(listItem);
// make sure the first remove button is enabled
this.updateRemoveButton();
checkActionsReorder();
]]>
</body>
</method>
<method name="removeRow">
<body>
<![CDATA[
// this.mListBox will fail after the row is removed, so save it
let listBox = this.mListBox;
if (listBox.getRowCount() > 1)
this.remove();
// can't use 'this' as it is destroyed now
listBox.getItemAtIndex(0).updateRemoveButton();
checkActionsReorder();
]]>
</body>
</method>
<method name="storeFocus">
<body>
<![CDATA[
// When this action row is focused, store its index in the parent listbox.
this.mListBox.setAttribute("focusedAction", this.mListBox.getIndexOfItem(this));
]]>
</body>
</method>
</implementation>
</binding>
<binding id="ruleactiontarget-base">
<implementation>
<constructor>
<![CDATA[
if (this.parentNode.hasAttribute('initialActionIndex'))
{
let actionIndex = this.parentNode.getAttribute('initialActionIndex');
let filterAction = gFilter.getActionAt(actionIndex);
this.parentNode.initWithAction(filterAction);
}
this.parentNode.updateRemoveButton();
]]>
</constructor>
</implementation>
</binding>
<binding id="ruleactiontarget-tag" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
<content>
<xul:menulist class="ruleactionitem">
<xul:menupopup>
</xul:menupopup>
</xul:menulist>
</content>
<implementation>
<constructor>
<![CDATA[
let menuPopup = document.getAnonymousNodes(this)[0].menupopup;
let tagArray = MailServices.tags.getAllTags({});
for (let i = 0; i < tagArray.length; ++i)
{
var taginfo = tagArray[i];
var newMenuItem = document.createElement('menuitem');
newMenuItem.setAttribute('label', taginfo.tag);
newMenuItem.setAttribute('value', taginfo.key);
menuPopup.appendChild(newMenuItem);
}
// propagating a pre-existing hack to make the tag get displayed correctly in the menulist
// now that we've changed the tags for each menu list. We need to use the current selectedIndex
// (if its defined) to handle the case where we were initialized with a filter action already.
var currentItem = document.getAnonymousNodes(this)[0].selectedItem;
document.getAnonymousNodes(this)[0].selectedItem = null;
document.getAnonymousNodes(this)[0].selectedItem = currentItem;
]]>
</constructor>
</implementation>
</binding>
<binding id="ruleactiontarget-priority" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
<content>
<xul:menulist class="ruleactionitem">
<xul:menupopup>
<xul:menuitem value="6" label="&highestPriorityCmd.label;"/>
<xul:menuitem value="5" label="&highPriorityCmd.label;"/>
<xul:menuitem value="4" label="&normalPriorityCmd.label;"/>
<xul:menuitem value="3" label="&lowPriorityCmd.label;"/>
<xul:menuitem value="2" label="&lowestPriorityCmd.label;"/>
</xul:menupopup>
</xul:menulist>
</content>
</binding>
<binding id="ruleactiontarget-junkscore" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
<content>
<xul:menulist class="ruleactionitem">
<xul:menupopup>
<xul:menuitem value="100" label="&junk.label;"/>
<xul:menuitem value="0" label="&notJunk.label;"/>
</xul:menupopup>
</xul:menulist>
</content>
</binding>
<binding id="ruleactiontarget-replyto" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
<content>
<xul:menulist class="ruleactionitem">
<xul:menupopup>
</xul:menupopup>
</xul:menulist>
</content>
<implementation>
<constructor>
<![CDATA[
document.getAnonymousElementByAttribute(
this.parentNode, "class", "ruleactiontype")
.getTemplates(true, document.getAnonymousNodes(this)[0]);
]]>
</constructor>
</implementation>
</binding>
<binding id="ruleactiontarget-forwardto" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
<content>
<xul:textbox class="ruleactionitem" flex="1"/>
</content>
</binding>
<binding id="ruleactiontarget-folder" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
<content>
<xul:menulist class="ruleactionitem folderMenuItem"
displayformat="verbose"
oncommand="this.parentNode.setPicker(event);">
<xul:menupopup type="folder"
mode="filing"
class="menulist-menupopup"
showRecent="true"
recentLabel="&recentFolders.label;"
showFileHereLabel="true"/>
</xul:menulist>
</content>
<implementation>
<constructor>
<![CDATA[
Components.utils.import("resource:///modules/MailUtils.js", this);
let folder = this.menulist.value ?
this.MailUtils.getFolderForURI(this.menulist.value) :
gFilterList.folder;
// An account folder is not a move/copy target; show "Choose Folder".
folder = folder.isServer ? null : folder;
let menupopup = this.menulist.menupopup;
// The menupopup constructor needs to finish first.
setTimeout(function() { menupopup.selectFolder(folder); }, 0);
]]>
</constructor>
<field name="menulist">document.getAnonymousNodes(this)[0]</field>
<method name="setPicker">
<parameter name="aEvent"/>
<body>
<![CDATA[
this.menulist.menupopup.selectFolder(aEvent.target._folder);
]]>
</body>
</method>
</implementation>
</binding>
</bindings>

View file

@ -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();
}

View file

@ -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>

View file

@ -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',
]

View file

@ -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);
};

View file

@ -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;
};

View file

@ -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);
};

View file

@ -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;
};

View file

@ -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);
};

View file

@ -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);
};

View file

@ -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);
};

View file

@ -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();
};

View file

@ -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);
};

View file

@ -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();
};

View file

@ -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;
};

View file

@ -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;
};

View file

@ -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);
};

View file

@ -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);
};

View file

@ -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);
};

View file

@ -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();
};

View file

@ -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);
};

View file

@ -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

View file

@ -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;
};

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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)"

View file

@ -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)"

View file

@ -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)\")"

View file

@ -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)"

View file

@ -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)"

View file

@ -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)"

View file

@ -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)"

View file

@ -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',
]

View file

@ -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);
}
}

File diff suppressed because it is too large Load diff

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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_

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -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;
}

View file

@ -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

View file

@ -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;
}

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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;
}

View file

@ -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

View file

@ -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);

View file

@ -0,0 +1,2 @@
component {A2E95F4F-DA72-4a41-9493-661AD353C00A} nsMsgTraitService.js
contract @mozilla.org/msg-trait-service;1 {A2E95F4F-DA72-4a41-9493-661AD353C00A}