gecko-dev/mailnews/base/search/src/nsMsgSearchTerm.cpp

1568 строки
42 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* The contents of this file are subject to the Netscape Public
* License Version 1.1 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.mozilla.org/NPL/
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1999 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
*/
#include "msgCore.h"
#include "xp_core.h"
#include "nsMsgSearchCore.h"
#include "nsMsgUtils.h"
#include "nsIMsgDatabase.h"
#include "nsMsgSearchTerm.h"
#include "nsMsgBodyHandler.h"
#include "nsMsgResultElement.h"
//---------------------------------------------------------------------------
// nsMsgSearchTerm specifies one criterion, e.g. name contains phil
//---------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-------------------- Implementation of nsMsgSearchTerm -----------------------
//-----------------------------------------------------------------------------
typedef struct
{
nsMsgSearchAttribute attrib;
const char *attribName;
} nsMsgSearchAttribEntry;
nsMsgSearchAttribEntry SearchAttribEntryTable[] =
{
{nsMsgSearchAttrib::Subject, "subject"},
{nsMsgSearchAttrib::Sender, "from"},
{nsMsgSearchAttrib::Body, "body"},
{nsMsgSearchAttrib::Date, "date"},
{nsMsgSearchAttrib::Priority, "priority"},
{nsMsgSearchAttrib::MsgStatus, "status"},
{nsMsgSearchAttrib::To, "to"},
{nsMsgSearchAttrib::CC, "CC"},
{nsMsgSearchAttrib::ToOrCC, "to or CC"}
};
// Take a string which starts off with an attribute
// return the matching attribute. If the string is not in the table, then we can conclude that it is an arbitrary header
nsresult NS_MsgGetAttributeFromString(const char *string, PRInt16 *attrib)
{
if (NULL == string || NULL == attrib)
return NS_ERROR_NULL_POINTER;
PRBool found = PR_FALSE;
for (int idxAttrib = 0; idxAttrib < (int)(sizeof(SearchAttribEntryTable) / sizeof(nsMsgSearchAttribEntry)); idxAttrib++)
{
if (!PL_strcasecmp(string, SearchAttribEntryTable[idxAttrib].attribName))
{
found = PR_TRUE;
*attrib = SearchAttribEntryTable[idxAttrib].attrib;
break;
}
}
if (!found)
*attrib = nsMsgSearchAttrib::OtherHeader; // assume arbitrary header if we could not find the header in the table
return NS_OK; // we always succeed now
}
nsresult NS_MsgGetStringForAttribute(PRInt16 attrib, const char **string)
{
if (NULL == string)
return NS_ERROR_NULL_POINTER;
PRBool found = PR_FALSE;
for (int idxAttrib = 0; idxAttrib < (int)(sizeof(SearchAttribEntryTable) / sizeof(nsMsgSearchAttribEntry)); idxAttrib++)
{
// I'm using the idx's as aliases into MSG_SearchAttribute and
// MSG_SearchOperator enums which is legal because of the way the
// enums are defined (starts at 0, numItems at end)
if (attrib == SearchAttribEntryTable[idxAttrib].attrib)
{
found = PR_TRUE;
*string = SearchAttribEntryTable[idxAttrib].attribName;
break;
}
}
// we no longer return invalid attribute. If we cannot find the string in the table,
// then it is an arbitrary header. Return success regardless if found or not
// return (found) ? SearchError_Success : SearchError_InvalidAttribute;
return NS_OK;
}
typedef struct
{
nsMsgSearchOperator op;
const char *opName;
} nsMsgSearchOperatorEntry;
nsMsgSearchOperatorEntry SearchOperatorEntryTable[] =
{
{nsMsgSearchOp::Contains, "contains"},
{nsMsgSearchOp::DoesntContain,"doesn't contain"},
{nsMsgSearchOp::Is, "is"},
{nsMsgSearchOp::Isnt, "isn't"},
{nsMsgSearchOp::IsEmpty, "is empty"},
{nsMsgSearchOp::IsBefore, "is before"},
{nsMsgSearchOp::IsAfter, "is after"},
{nsMsgSearchOp::IsHigherThan, "is higher than"},
{nsMsgSearchOp::IsLowerThan, "is lower than"},
{nsMsgSearchOp::BeginsWith, "begins with"},
{nsMsgSearchOp::EndsWith, "ends with"}
};
nsresult NS_MsgGetOperatorFromString(const char *string, PRInt16 *op)
{
if (NULL == string || NULL == op)
return NS_ERROR_NULL_POINTER;
PRBool found = PR_FALSE;
for (unsigned int idxOp = 0; idxOp < sizeof(SearchOperatorEntryTable) / sizeof(nsMsgSearchOperatorEntry); idxOp++)
{
// I'm using the idx's as aliases into MSG_SearchAttribute and
// MSG_SearchOperator enums which is legal because of the way the
// enums are defined (starts at 0, numItems at end)
if (!PL_strcasecmp(string, SearchOperatorEntryTable[idxOp].opName))
{
found = PR_TRUE;
*op = SearchOperatorEntryTable[idxOp].op;
break;
}
}
return (found) ? NS_OK : NS_ERROR_INVALID_ARG;
}
nsresult NS_MsgGetStringForOperator(PRInt16 op, const char **string)
{
if (NULL == string)
return NS_ERROR_NULL_POINTER;
PRBool found = PR_FALSE;
for (unsigned int idxOp = 0; idxOp < sizeof(SearchOperatorEntryTable) / sizeof(nsMsgSearchOperatorEntry); idxOp++)
{
// I'm using the idx's as aliases into MSG_SearchAttribute and
// MSG_SearchOperator enums which is legal because of the way the
// enums are defined (starts at 0, numItems at end)
if (op == SearchOperatorEntryTable[idxOp].op)
{
found = PR_TRUE;
*string = SearchOperatorEntryTable[idxOp].opName;
break;
}
}
return (found) ? NS_OK : NS_ERROR_INVALID_ARG;
}
void NS_MsgGetUntranslatedStatusName (uint32 s, nsCString *outName)
{
char *tmpOutName = NULL;
#define MSG_STATUS_MASK (MSG_FLAG_READ | MSG_FLAG_REPLIED | MSG_FLAG_FORWARDED | MSG_FLAG_NEW)
PRUint32 maskOut = (s & MSG_STATUS_MASK);
// diddle the flags to pay attention to the most important ones first, if multiple
// flags are set. Should remove this code from the winfe.
if (maskOut & MSG_FLAG_NEW)
maskOut = MSG_FLAG_NEW;
if ( maskOut & MSG_FLAG_REPLIED &&
maskOut & MSG_FLAG_FORWARDED )
maskOut = MSG_FLAG_REPLIED|MSG_FLAG_FORWARDED;
else if ( maskOut & MSG_FLAG_FORWARDED )
maskOut = MSG_FLAG_FORWARDED;
else if ( maskOut & MSG_FLAG_REPLIED )
maskOut = MSG_FLAG_REPLIED;
switch (maskOut)
{
case MSG_FLAG_READ:
tmpOutName = "read";
break;
case MSG_FLAG_REPLIED:
tmpOutName = "replied";
break;
case MSG_FLAG_FORWARDED:
tmpOutName = "forwarded";
break;
case MSG_FLAG_FORWARDED|MSG_FLAG_REPLIED:
tmpOutName = "replied and forwarded";
break;
case MSG_FLAG_NEW:
tmpOutName = "new";
break;
default:
// This is fine, status may be "unread" for example
break;
}
if (tmpOutName)
*outName = tmpOutName;
}
PRInt32 NS_MsgGetStatusValueFromName(char *name)
{
if (PL_strcmp("read", name))
return MSG_FLAG_READ;
if (PL_strcmp("replied", name))
return MSG_FLAG_REPLIED;
if (PL_strcmp("forwarded", name))
return MSG_FLAG_FORWARDED;
if (PL_strcmp("replied and forwarded", name))
return MSG_FLAG_FORWARDED|MSG_FLAG_REPLIED;
if (PL_strcmp("new", name))
return MSG_FLAG_NEW;
return 0;
}
// Needed for DeStream method.
nsMsgSearchTerm::nsMsgSearchTerm()
{
}
#if 0
nsMsgSearchTerm::nsMsgSearchTerm (
nsMsgSearchAttribute attrib,
nsMsgSearchOperator op,
nsMsgSearchValue *val,
PRBool booleanAND,
char * arbitraryHeader)
{
m_operator = op;
m_booleanOp = (booleanAND) ? nsMsgSearchBooleanOp::BooleanAND : nsMsgSearchBooleanOp::BooleanOR;
if (attrib == nsMsgSearchAttrib::OtherHeader && arbitraryHeader)
m_arbitraryHeader = arbitraryHeader;
m_attribute = attrib;
nsMsgResultElement::AssignValues (val, &m_value);
}
#endif
nsMsgSearchTerm::nsMsgSearchTerm (
nsMsgSearchAttribute attrib,
nsMsgSearchOperator op,
nsMsgSearchValue *val,
nsMsgSearchBooleanOperator boolOp,
char * arbitraryHeader)
{
m_operator = op;
m_attribute = attrib;
m_booleanOp = boolOp;
if (attrib == nsMsgSearchAttrib::OtherHeader && arbitraryHeader)
m_arbitraryHeader = arbitraryHeader;
nsMsgResultElement::AssignValues (val, &m_value);
}
nsMsgSearchTerm::~nsMsgSearchTerm ()
{
if (IS_STRING_ATTRIBUTE (m_attribute))
PR_Free(m_value.u.string);
}
// Perhaps we could find a better place for this?
// Caller needs to free.
/* static */char *nsMsgSearchTerm::EscapeQuotesInStr(const char *str)
{
int numQuotes = 0;
for (const char *strPtr = str; *strPtr; strPtr++)
if (*strPtr == '"')
numQuotes++;
int escapedStrLen = PL_strlen(str) + numQuotes;
char *escapedStr = (char *) PR_Malloc(escapedStrLen + 1);
if (escapedStr)
{
char *destPtr;
for (destPtr = escapedStr; *str; str++)
{
if (*str == '"')
*destPtr++ = '\\';
*destPtr++ = *str;
}
*destPtr = '\0';
}
return escapedStr;
}
nsresult nsMsgSearchTerm::OutputValue(nsCString &outputStr)
{
if (IS_STRING_ATTRIBUTE(m_attribute))
{
PRBool quoteVal = PR_FALSE;
// need to quote strings with ')' - filter code will escape quotes
if (PL_strchr(m_value.u.string, ')'))
{
quoteVal = PR_TRUE;
outputStr += "\"";
}
if (PL_strchr(m_value.u.string, '"'))
{
char *escapedString = nsMsgSearchTerm::EscapeQuotesInStr(m_value.u.string);
if (escapedString)
{
outputStr += escapedString;
PR_Free(escapedString);
}
}
else
{
outputStr += m_value.u.string;
}
if (quoteVal)
outputStr += "\"";
}
else
{
switch (m_attribute)
{
case nsMsgSearchAttrib::Date:
{
PRExplodedTime exploded;
PR_ExplodeTime(m_value.u.date, PR_LocalTimeParameters, &exploded);
// wow, so tm_mon is 0 based, tm_mday is 1 based.
char dateBuf[100];
PR_FormatTimeUSEnglish (dateBuf, sizeof(dateBuf), "%d-%b-%Y", &exploded);
outputStr += dateBuf;
break;
}
case nsMsgSearchAttrib::MsgStatus:
{
nsCAutoString status;
NS_MsgGetUntranslatedStatusName (m_value.u.msgStatus, &status);
outputStr += status;
break;
}
case nsMsgSearchAttrib::Priority:
{
nsAutoString priority;
NS_MsgGetUntranslatedPriorityName( m_value.u.priority,
&priority);
outputStr += nsCAutoString(priority);
break;
}
default:
NS_ASSERTION(PR_FALSE, "trying to output invalid attribute");
break;
}
}
return NS_OK;
}
nsresult nsMsgSearchTerm::EnStreamNew (nsCString &outStream)
{
const char *attrib, *operatorStr;
nsCAutoString outputStr;
nsresult ret;
ret = NS_MsgGetStringForAttribute(m_attribute, &attrib);
if (ret != NS_OK)
return ret;
if (m_attribute == nsMsgSearchAttrib::OtherHeader) // if arbitrary header, use it instead!
{
outputStr = "\"";
outputStr += m_arbitraryHeader;
outputStr += "\"";
}
else
outputStr = attrib;
outputStr += ',';
ret = NS_MsgGetStringForOperator(m_operator, &operatorStr);
if (ret != NS_OK)
return ret;
outputStr += operatorStr;
outputStr += ',';
OutputValue(outputStr);
outStream = outputStr;
return NS_OK;
}
// fill in m_value from the input stream.
nsresult nsMsgSearchTerm::ParseValue(char *inStream)
{
if (IS_STRING_ATTRIBUTE(m_attribute))
{
PRBool quoteVal = PR_FALSE;
while (IS_SPACE(*inStream))
inStream++;
// need to remove pair of '"', if present
if (*inStream == '"')
{
quoteVal = PR_TRUE;
inStream++;
}
int valueLen = PL_strlen(inStream);
if (quoteVal && inStream[valueLen - 1] == '"')
valueLen--;
m_value.u.string = (char *) PR_Malloc(valueLen + 1);
PL_strncpy(m_value.u.string, inStream, valueLen + 1);
m_value.u.string[valueLen] = '\0';
}
else
{
switch (m_attribute)
{
case nsMsgSearchAttrib::Date:
PR_ParseTimeString (inStream, PR_FALSE, &m_value.u.date);
break;
case nsMsgSearchAttrib::MsgStatus:
m_value.u.msgStatus = NS_MsgGetStatusValueFromName(inStream);
break;
case nsMsgSearchAttrib::Priority:
NS_MsgGetPriorityFromString(inStream, &m_value.u.priority);
break;
default:
NS_ASSERTION(PR_FALSE, "invalid attribute parsing search term value");
break;
}
}
m_value.attribute = m_attribute;
return NS_OK;
}
// find the operator code for this operator string.
nsMsgSearchOperator nsMsgSearchTerm::ParseOperator(char *inStream)
{
PRInt16 operatorVal;
nsresult err;
while (IS_SPACE(*inStream))
inStream++;
char *commaSep = PL_strchr(inStream, ',');
if (commaSep)
*commaSep = '\0';
err = NS_MsgGetOperatorFromString(inStream, &operatorVal);
return (nsMsgSearchOperator) operatorVal;
}
// find the attribute code for this comma-delimited attribute.
nsMsgSearchAttribute nsMsgSearchTerm::ParseAttribute(char *inStream)
{
nsCAutoString attributeStr;
PRInt16 attributeVal;
nsresult err;
while (IS_SPACE(*inStream))
inStream++;
// if we are dealing with an arbitrary header, it may be quoted....
PRBool quoteVal = PR_FALSE;
if (*inStream == '"')
{
quoteVal = PR_TRUE;
inStream++;
}
char *separator;
if (quoteVal) // arbitrary headers are quoted...
separator = PL_strchr(inStream, '"');
else
separator = PL_strchr(inStream, ',');
if (separator)
*separator = '\0';
err = NS_MsgGetAttributeFromString(inStream, &attributeVal);
nsMsgSearchAttribute attrib = (nsMsgSearchAttribute) attributeVal;
if (attrib == nsMsgSearchAttrib::OtherHeader) // if we are dealing with an arbitrary header....
m_arbitraryHeader = inStream;
return attrib;
}
// De stream one search term. If the condition looks like
// condition = "(to or CC, contains, r-thompson) AND (body, doesn't contain, fred)"
// This routine should get called twice, the first time
// with "to or CC, contains, r-thompson", the second time with
// "body, doesn't contain, fred"
nsresult nsMsgSearchTerm::DeStreamNew (char *inStream, PRInt16 /*length*/)
{
char *commaSep = PL_strchr(inStream, ',');
m_attribute = ParseAttribute(inStream); // will allocate space for arbitrary header if necessary
if (!commaSep)
return NS_ERROR_INVALID_ARG;
char *secondCommaSep = PL_strchr(commaSep + 1, ',');
if (commaSep)
m_operator = ParseOperator(commaSep + 1);
if (secondCommaSep)
ParseValue(secondCommaSep + 1);
return NS_OK;
}
void nsMsgSearchTerm::StripQuotedPrintable (unsigned char *src)
{
// decode quoted printable text in place
unsigned char *dest = src;
int srcIdx = 0, destIdx = 0;
while (src[srcIdx] != 0)
{
if (src[srcIdx] == '=')
{
unsigned char *token = &src[srcIdx];
unsigned char c = 0;
// decode the first quoted char
if (token[1] >= '0' && token[1] <= '9')
c = token[1] - '0';
else if (token[1] >= 'A' && token[1] <= 'F')
c = token[1] - ('A' - 10);
else if (token[1] >= 'a' && token[1] <= 'f')
c = token[1] - ('a' - 10);
else
{
// first char after '=' isn't hex. copy the '=' as a normal char and keep going
dest[destIdx++] = src[srcIdx++]; // aka token[0]
continue;
}
// decode the second quoted char
c = (c << 4);
if (token[2] >= '0' && token[2] <= '9')
c += token[2] - '0';
else if (token[2] >= 'A' && token[2] <= 'F')
c += token[2] - ('A' - 10);
else if (token[2] >= 'a' && token[2] <= 'f')
c += token[2] - ('a' - 10);
else
{
// second char after '=' isn't hex. copy the '=' as a normal char and keep going
dest[destIdx++] = src[srcIdx++]; // aka token[0]
continue;
}
// if we got here, we successfully decoded a quoted printable sequence,
// so bump each pointer past it and move on to the next char;
dest[destIdx++] = c;
srcIdx += 3;
}
else
dest[destIdx++] = src[srcIdx++];
}
dest[destIdx] = src[srcIdx]; // null terminate
}
// Looks in the MessageDB for the user specified arbitrary header, if it finds the header, it then looks for a match against
// the value for the header.
nsresult nsMsgSearchTerm::MatchArbitraryHeader (nsMsgSearchScopeTerm *scope, PRUint32 offset, PRUint32 length /* in lines*/, const char *charset,
nsIMsgDBHdr *msg, nsIMsgDatabase* db, const char * headers,
PRUint32 headersSize, PRBool ForFiltering, PRBool *pResult)
{
if (!pResult)
return NS_ERROR_NULL_POINTER;
*pResult = PR_FALSE;
nsresult err = NS_OK;
PRBool result;
nsMsgBodyHandler * bodyHandler = new nsMsgBodyHandler (scope, offset,length, msg, db, headers, headersSize, ForFiltering);
if (!bodyHandler)
return NS_ERROR_OUT_OF_MEMORY;
bodyHandler->SetStripHeaders (PR_FALSE);
if (MatchAllBeforeDeciding())
result = PR_TRUE;
else
result = PR_FALSE;
const int kBufSize = 512; // max size of a line??
char * buf = (char *) PR_Malloc(kBufSize);
if (buf)
{
PRBool searchingHeaders = PR_TRUE;
while (searchingHeaders && bodyHandler->GetNextLine(buf, kBufSize))
{
char * buf_end = buf + PL_strlen(buf);
int headerLength = m_arbitraryHeader.Length();
if (m_arbitraryHeader.Equals(buf))
{
char * headerValue = buf + headerLength; // value occurs after the header name...
if (headerValue < buf_end && headerValue[0] == ':') // + 1 to account for the colon which is MANDATORY
headerValue++;
// strip leading white space
while (headerValue < buf_end && IS_SPACE(*headerValue))
headerValue++; // advance to next character
// strip trailing white space
char * end = buf_end - 1;
while (end > headerValue && IS_SPACE(*end)) // while we haven't gone back past the start and we are white space....
{
*end = '\0'; // eat up the white space
end--; // move back and examine the previous character....
}
if (headerValue < buf_end && *headerValue) // make sure buf has info besides just the header
{
nsCAutoString headerStr (headerValue);
PRBool result2;
err = MatchString(&headerStr, charset, PR_FALSE, &result2); // match value with the other info...
if (result != result2) // if we found a match
{
searchingHeaders = PR_FALSE; // then stop examining the headers
result = result2;
}
}
else
NS_ASSERTION(PR_FALSE, "error matching arbitrary headers"); // mscott --> i'd be curious if there is a case where this fails....
}
if (EMPTY_MESSAGE_LINE(buf))
searchingHeaders = PR_FALSE;
}
delete bodyHandler;
PR_Free(buf);
*pResult = result;
return err;
}
else
{
delete bodyHandler;
return NS_ERROR_OUT_OF_MEMORY;
}
}
nsresult nsMsgSearchTerm::MatchBody (nsMsgSearchScopeTerm *scope, PRUint32 offset, PRUint32 length /*in lines*/, const char *folderCharset,
nsIMsgDBHdr *msg, nsIMsgDatabase* db, PRBool *pResult)
{
if (!pResult)
return NS_ERROR_NULL_POINTER;
nsresult err = NS_OK;
PRBool result = PR_FALSE;
*pResult = PR_FALSE;
// Small hack so we don't look all through a message when someone has
// specified "BODY IS foo"
if ((length > 0) && (m_operator == nsMsgSearchOp::Is || m_operator == nsMsgSearchOp::Isnt))
length = PL_strlen (m_value.u.string);
nsMsgBodyHandler * bodyHan = new nsMsgBodyHandler (scope, offset, length, msg, db);
if (!bodyHan)
return NS_ERROR_OUT_OF_MEMORY;
const int kBufSize = 512; // max size of a line???
char *buf = (char*) PR_Malloc(kBufSize);
if (buf)
{
PRBool endOfFile = PR_FALSE; // if retValue == 0, we've hit the end of the file
uint32 lines = 0;
PRBool getConverter = PR_FALSE;
#ifdef DO_I18N
CCCDataObject conv = INTL_CreateCharCodeConverter();
PRInt16 win_csid = INTL_DocToWinCharSetID(foldcsid);
PRInt16 mail_csid = INTL_DefaultMailCharSetID(win_csid); // to default mail_csid (e.g. JIS for Japanese)
if ((nsnull != conv) && INTL_GetCharCodeConverter(mail_csid, win_csid, conv))
getConverter = PR_TRUE;
#endif // DO_I18N
// Change the sense of the loop so we don't bail out prematurely
// on negative terms. i.e. opDoesntContain must look at all lines
PRBool boolContinueLoop;
if (MatchAllBeforeDeciding())
result = boolContinueLoop = PR_TRUE;
else
result = boolContinueLoop = PR_FALSE;
// If there's a '=' in the search term, then we're not going to do
// quoted printable decoding. Otherwise we assume everything is
// quoted printable. Obviously everything isn't quoted printable, but
// since we don't have a MIME parser handy, and we want to err on the
// side of too many hits rather than not enough, we'll assume in that
// general direction. Blech. ### FIX ME
// bug fix #88935: for stateful csids like JIS, we don't want to decode
// quoted printable since it contains '='.
PRBool isQuotedPrintable = /*!(mail_csid & STATEFUL) && */
(PL_strchr (m_value.u.string, '=') == nsnull);
while (!endOfFile && result == boolContinueLoop)
{
if (bodyHan->GetNextLine(buf, kBufSize) >= 0)
{
// Do in-place decoding of quoted printable
if (isQuotedPrintable)
StripQuotedPrintable ((unsigned char*)buf);
nsCString compare(buf);
if (getConverter)
{
#ifdef DO_I18N
// In here we do I18N conversion if we get the converter
char *newBody = nsnull;
newBody = (char *)INTL_CallCharCodeConverter(conv, (unsigned char *) buf, (int32) PL_strlen(buf));
if (newBody && (newBody != buf))
{
// CharCodeConverter return the char* to the orginal string
// we don't want to free body in that case
compare = newBody;
}
#endif
}
char startChar = (char) compare.CharAt(0);
if (startChar != CR && startChar != LF)
{
err = MatchString (&compare, nsnull, PR_TRUE, &result);
lines++;
}
}
else
endOfFile = PR_TRUE;
}
#ifdef DO_I18N
if(conv)
INTL_DestroyCharCodeConverter(conv);
#endif
PR_FREEIF(buf);
delete bodyHan;
}
else
err = NS_ERROR_OUT_OF_MEMORY;
*pResult = result;
return err;
}
// *pResult is PR_FALSE when strings don't match, PR_TRUE if they do.
nsresult nsMsgSearchTerm::MatchString (nsCString *stringToMatch, const char *charset, PRBool body, PRBool *pResult)
{
if (!pResult)
return NS_ERROR_NULL_POINTER;
PRBool result = PR_FALSE;
nsresult err = NS_OK;
nsCAutoString n_str;
const char* n_header = nsnull;
if(nsMsgSearchOp::IsEmpty != m_operator) // Save some performance for opIsEmpty
{
#ifdef DO_I18N
n_str = INTL_GetNormalizeStr(csid , (unsigned char*)m_value.u.string); // Always new buffer unless not enough memory
if (!body)
n_header = INTL_GetNormalizeStrFromRFC1522(csid , (unsigned char*)stringToMatch); // Always new buffer unless not enough memory
else
n_header = INTL_GetNormalizeStr(csid , (unsigned char*)stringToMatch); // Always new buffer unless not enough memory
NS_ASSERTION(n_str, "failed get normalized string");
NS_ASSERTION(n_header, "failed get normalized header");
#else
n_header = *stringToMatch;
n_str = m_value.u.string;
#endif // DO_I18N
}
switch (m_operator)
{
case nsMsgSearchOp::Contains:
if ((nsnull != n_header) && ((n_str.GetBuffer())[0]) && /* INTL_StrContains(csid, n_header, n_str) */
stringToMatch->Find(n_str, PR_TRUE) != -1)
result = PR_TRUE;
break;
case nsMsgSearchOp::DoesntContain:
if ((nsnull != n_header) && ((n_str.GetBuffer())[0]) && /* !INTL_StrContains(csid, n_header, n_str) */
stringToMatch->Find(n_str, PR_TRUE) == -1)
result = PR_TRUE;
break;
case nsMsgSearchOp::Is:
if(n_header)
{
if ((n_str.GetBuffer())[0])
{
if (n_str.Equals(*stringToMatch, PR_TRUE /*ignore case*/) /* INTL_StrIs(csid, n_header, n_str)*/ )
result = PR_TRUE;
}
else if (n_header[0] == '\0') // Special case for "is <the empty string>"
result = PR_TRUE;
}
break;
case nsMsgSearchOp::Isnt:
if(n_header)
{
if ((n_str.GetBuffer())[0])
{
if (!n_str.Equals(*stringToMatch, PR_TRUE)/* INTL_StrIs(csid, n_header, n_str)*/ )
result = PR_TRUE;
}
else if (n_header[0] != '\0') // Special case for "isn't <the empty string>"
result = PR_TRUE;
}
break;
case nsMsgSearchOp::IsEmpty:
if (stringToMatch->Length() == 0)
result = PR_TRUE;
break;
case nsMsgSearchOp::BeginsWith:
#ifdef DO_I18N_YET
if((nsnull != n_str) && (nsnull != n_header) && INTL_StrBeginWith(csid, n_header, n_str))
result = PR_TRUE;
#else
// ### DMB - not the most efficient way to do this.
if (stringToMatch->Find(n_str, PR_TRUE) == 0)
result = PR_TRUE;
#endif
break;
case nsMsgSearchOp::EndsWith:
#ifdef DO_I18N_YET
{
if((nsnull != n_str) && (nsnull != n_header) && INTL_StrEndWith(csid, n_header, n_str))
result = PR_TRUE;
}
#else
NS_ASSERTION(PR_FALSE, "not implemented yet");
#endif
break;
default:
NS_ASSERTION(PR_FALSE, "invalid operator matching search results");
}
*pResult = result;
return err;
}
PRBool nsMsgSearchTerm::MatchAllBeforeDeciding ()
{
if (m_operator == nsMsgSearchOp::DoesntContain || m_operator == nsMsgSearchOp::Isnt)
return PR_TRUE;
return PR_FALSE;
}
nsresult nsMsgSearchTerm::MatchRfc822String (const char *string, const char *charset, PRBool *pResult)
{
if (!pResult)
return NS_ERROR_NULL_POINTER;
*pResult = PR_FALSE;
PRBool result;
nsresult err = InitHeaderAddressParser();
if (!NS_SUCCEEDED(err))
return err;
// Isolate the RFC 822 parsing weirdnesses here. MSG_ParseRFC822Addresses
// returns a catenated string of null-terminated strings, which we walk
// across, tring to match the target string to either the name OR the address
char *names = nsnull, *addresses = nsnull;
// Change the sense of the loop so we don't bail out prematurely
// on negative terms. i.e. opDoesntContain must look at all recipients
PRBool boolContinueLoop;
if (MatchAllBeforeDeciding())
result = boolContinueLoop = PR_TRUE;
else
result = boolContinueLoop = PR_FALSE;
PRUint32 count;
nsresult parseErr = m_headerAddressParser->ParseHeaderAddresses(charset, string, &names, &addresses, &count) ;
if (NS_SUCCEEDED(parseErr) && count > 0)
{
NS_ASSERTION(names, "couldn't get names");
NS_ASSERTION(addresses, "couldn't get addresses");
if (!names || !addresses)
return err;
nsCAutoString walkNames(names);
nsCAutoString walkAddresses(addresses);
PRInt32 namePos = 0;
PRInt32 addressPos = 0;
for (PRUint32 i = 0; i < count && result == boolContinueLoop; i++)
{
err = MatchString (&walkNames, charset, PR_FALSE, &result);
if (boolContinueLoop == result)
err = MatchString (&walkAddresses, charset, PR_FALSE, &result);
namePos += walkNames.Length() + 1;
addressPos += walkAddresses.Length() + 1;
walkNames = names + namePos;
walkAddresses = addresses + addressPos;;
}
PR_FREEIF(names);
PR_FREEIF(addresses);
}
*pResult = result;
return err;
}
nsresult nsMsgSearchTerm::GetLocalTimes (PRTime a, PRTime b, PRExplodedTime &aExploded, PRExplodedTime &bExploded)
{
PR_ExplodeTime(a, PR_LocalTimeParameters, &aExploded);
PR_ExplodeTime(b, PR_LocalTimeParameters, &bExploded);
return NS_OK;
}
nsresult nsMsgSearchTerm::MatchDate (PRTime dateToMatch, PRBool *pResult)
{
if (!pResult)
return NS_ERROR_NULL_POINTER;
nsresult err = NS_OK;
PRBool result = PR_FALSE;
nsTime t_date(dateToMatch);
switch (m_operator)
{
case nsMsgSearchOp::IsBefore:
if (t_date < nsTime(m_value.u.date))
result = PR_TRUE;
break;
case nsMsgSearchOp::IsAfter:
{
nsTime adjustedDate = nsTime(m_value.u.date);
adjustedDate += 60*60*24; // we want to be greater than the next day....
if (t_date > adjustedDate)
result = PR_TRUE;
}
break;
case nsMsgSearchOp::Is:
{
PRExplodedTime tmToMatch, tmThis;
if (NS_OK == GetLocalTimes (dateToMatch, m_value.u.date, tmToMatch, tmThis))
{
if (tmThis.tm_year == tmToMatch.tm_year &&
tmThis.tm_month == tmToMatch.tm_month &&
tmThis.tm_mday == tmToMatch.tm_mday)
result = PR_TRUE;
}
}
break;
case nsMsgSearchOp::Isnt:
{
PRExplodedTime tmToMatch, tmThis;
if (NS_OK == GetLocalTimes (dateToMatch, m_value.u.date, tmToMatch, tmThis))
{
if (tmThis.tm_year != tmToMatch.tm_year ||
tmThis.tm_month != tmToMatch.tm_month ||
tmThis.tm_mday != tmToMatch.tm_mday)
result = PR_TRUE;
}
}
break;
default:
NS_ASSERTION(PR_FALSE, "invalid compare op for dates");
}
*pResult = result;
return err;
}
nsresult nsMsgSearchTerm::MatchAge (PRTime msgDate, PRBool *pResult)
{
if (!pResult)
return NS_ERROR_NULL_POINTER;
PRBool result = PR_FALSE;
nsresult err = NS_OK;
#ifdef DO_AGE_YET
time_t now = XP_TIME();
time_t matchDay = now - (m_value.u.age * 60 * 60 * 24);
struct tm * matchTime = localtime(&matchDay);
// localTime axes previous results so save these.
int day = matchTime->tm_mday;
int month = matchTime->tm_mon;
int year = matchTime->tm_year;
struct tm * msgTime = localtime(&msgDate);
switch (m_operator)
{
case nsMsgSearchOp::IsGreaterThan: // is older than
if (msgDate < matchDay)
result = PR_TRUE;
break;
case nsMsgSearchOp::IsLessThan: // is younger than
if (msgDate > matchDay)
result = PR_TRUE;
break;
case nsMsgSearchOp::Is:
if (matchTime && msgTime)
if ((day == msgTime->tm_mday)
&& (month == msgTime->tm_mon)
&& (year == msgTime->tm_year))
result = PR_TRUE;
break;
default:
NS_ASSERTION(PR_FALSE, "invalid compare op comparing msg age");
}
#endif // DO_AGE_YET
*pResult = result;
return err;
}
nsresult nsMsgSearchTerm::MatchSize (PRUint32 sizeToMatch, PRBool *pResult)
{
if (!pResult)
return NS_ERROR_NULL_POINTER;
PRBool result = PR_FALSE;
switch (m_operator)
{
case nsMsgSearchOp::IsHigherThan:
if (sizeToMatch > m_value.u.size)
result = PR_TRUE;
break;
case nsMsgSearchOp::IsLowerThan:
if (sizeToMatch < m_value.u.size)
result = PR_TRUE;
break;
default:
break;
}
*pResult = result;
return NS_OK;
}
nsresult nsMsgSearchTerm::MatchStatus (PRUint32 statusToMatch, PRBool *pResult)
{
if (!pResult)
return NS_ERROR_NULL_POINTER;
nsresult err = NS_OK;
PRBool matches = PR_FALSE;
if (statusToMatch & m_value.u.msgStatus)
matches = PR_TRUE;
switch (m_operator)
{
case nsMsgSearchOp::Is:
if (matches)
*pResult = PR_TRUE;
break;
case nsMsgSearchOp::Isnt:
if (!matches)
*pResult = PR_TRUE;
break;
default:
*pResult = PR_FALSE;
err = NS_ERROR_FAILURE;
NS_ASSERTION(PR_FALSE, "invalid comapre op for msg status");
}
return err;
}
nsresult nsMsgSearchTerm::MatchPriority (nsMsgPriority priorityToMatch, PRBool *pResult)
{
if (!pResult)
return NS_ERROR_NULL_POINTER;
nsresult err = NS_OK;
PRBool result=NS_OK;
// Use this ugly little hack to get around the fact that enums don't have
// integer compare operators
int p1 = (int) priorityToMatch;
int p2 = (int) m_value.u.priority;
switch (m_operator)
{
case nsMsgSearchOp::IsHigherThan:
if (p1 > p2)
result = PR_TRUE;
break;
case nsMsgSearchOp::IsLowerThan:
if (p1 < p2)
result = PR_TRUE;
break;
case nsMsgSearchOp::Is:
if (p1 == p2)
result = PR_TRUE;
break;
default:
result = PR_FALSE;
err = NS_ERROR_FAILURE;
NS_ASSERTION(PR_FALSE, "invalid match operator");
}
*pResult = result;
return err;
}
// Lazily initialize the rfc822 header parser we're going to use to do
// header matching.
nsresult nsMsgSearchTerm::InitHeaderAddressParser()
{
nsresult res = NS_OK;
NS_DEFINE_CID(kMsgHeaderParserCID, NS_MSGHEADERPARSER_CID);
if (!m_headerAddressParser)
{
res = nsComponentManager::CreateInstance(kMsgHeaderParserCID,
nsnull,
nsIMsgHeaderParser::GetIID(),
(void **) getter_AddRefs(m_headerAddressParser));
}
return res;
}
//-----------------------------------------------------------------------------
// nsMsgSearchScopeTerm implementation
//-----------------------------------------------------------------------------
nsMsgSearchScopeTerm::nsMsgSearchScopeTerm (nsMsgSearchScopeAttribute attribute, nsIMsgFolder *folder)
{
m_attribute = attribute;
m_folder = folder;
m_searchServer = PR_TRUE;
m_fileStream = nsnull;
}
nsMsgSearchScopeTerm::nsMsgSearchScopeTerm ()
{
m_searchServer = PR_TRUE;
m_fileStream = nsnull;
}
nsMsgSearchScopeTerm::~nsMsgSearchScopeTerm ()
{
}
// ### purely temporary
static PRBool NET_IsOffline()
{
return PR_FALSE;
}
PRBool nsMsgSearchScopeTerm::IsOfflineNews()
{
switch (m_attribute)
{
case nsMsgSearchScope::Newsgroup:
case nsMsgSearchScope::AllSearchableGroups:
if (NET_IsOffline() || !m_searchServer)
return PR_TRUE;
else
return PR_FALSE;
case nsMsgSearchScope::OfflineNewsgroup:
return PR_TRUE;
default:
return PR_FALSE;
}
}
PRBool nsMsgSearchScopeTerm::IsOfflineMail ()
{
// Find out whether "this" mail folder is online or offline
NS_ASSERTION(m_folder, "scope doesn't have folder");
// if (m_folder->GetType() == FOLDER_IMAPMAIL && !NET_IsOffline() && m_searchServer) // make sure we are not in offline IMAP (mscott)
// return PR_FALSE;
return PR_TRUE; // if POP or IMAP in offline mode
}
PRBool nsMsgSearchScopeTerm::IsOfflineIMAPMail()
{
// Find out whether "this" mail folder is an offline IMAP folder
NS_ASSERTION(m_folder, "scope doesn't have folder");
// if (m_folder->GetType() == FOLDER_IMAPMAIL && (NET_IsOffline() || !m_searchServer))
// return PR_TRUE;
return PR_FALSE; // we are not an IMAP folder that is offline
}
nsresult nsMsgSearchScopeTerm::GetMailPath(nsIFileSpec **aFileSpec)
{
return (m_folder) ? m_folder->GetPath(aFileSpec) : NS_ERROR_NULL_POINTER;
}
nsresult nsMsgSearchScopeTerm::TimeSlice ()
{
return NS_OK;
}
nsresult nsMsgSearchScopeTerm::InitializeAdapter (nsMsgSearchTermArray &termList)
{
return NS_OK;
}
char *nsMsgSearchScopeTerm::GetStatusBarName ()
{
return nsnull;
}
//-----------------------------------------------------------------------------
// nsMsgResultElement implementation
//-----------------------------------------------------------------------------
nsMsgResultElement::nsMsgResultElement(nsIMsgSearchAdapter *adapter)
{
m_adapter = adapter;
}
nsMsgResultElement::~nsMsgResultElement ()
{
for (int i = 0; i < m_valueList.Count(); i++)
{
nsMsgSearchValue *value = m_valueList.ElementAt(i);
#ifdef HUH_WHATS_THIS
if (value->attribute == nsMsgSearchAttrib::JpegFile)
{
char *url = XP_PlatformFileToURL (value->u.string);
char *tmp = url + PL_strlen("file://");
XP_FileRemove (tmp, xpMailFolder /*###phil hacky*/);
XP_FREE(url);
}
#endif
nsMsgResultElement::DestroyValue (value);
}
}
nsresult nsMsgResultElement::AddValue (nsMsgSearchValue *value)
{
m_valueList.AppendElement (value);
return NS_OK;
}
nsresult nsMsgResultElement::DestroyValue (nsMsgSearchValue *value)
{
if (IS_STRING_ATTRIBUTE(value->attribute))
{
NS_ASSERTION(value->u.string, "got null result value string");
PR_Free (value->u.string);
}
delete value;
return NS_OK;
}
nsresult nsMsgResultElement::AssignValues (nsMsgSearchValue *src, nsMsgSearchValue *dst)
{
// Yes, this could be an operator overload, but nsMsgSearchValue is totally public, so I'd
// have to define a derived class with nothing by operator=, and that seems like a bit much
nsresult err = NS_OK;
switch (src->attribute)
{
case nsMsgSearchAttrib::Priority:
dst->attribute = src->attribute;
dst->u.priority = src->u.priority;
break;
case nsMsgSearchAttrib::Date:
dst->attribute = src->attribute;
dst->u.date = src->u.date;
break;
case nsMsgSearchAttrib::MsgStatus:
dst->attribute = src->attribute;
dst->u.msgStatus = src->u.msgStatus;
break;
case nsMsgSearchAttrib::MessageKey:
dst->attribute = src->attribute;
dst->u.key = src->u.key;
break;
case nsMsgSearchAttrib::AgeInDays:
dst->attribute = src->attribute;
dst->u.age = src->u.age;
break;
default:
if (src->attribute < nsMsgSearchAttrib::kNumMsgSearchAttributes)
{
NS_ASSERTION(IS_STRING_ATTRIBUTE(src->attribute), "assigning non-string result");
dst->attribute = src->attribute;
dst->u.string = PL_strdup(src->u.string);
if (!dst->u.string)
err = NS_ERROR_OUT_OF_MEMORY;
}
else
err = NS_ERROR_INVALID_ARG;
}
return err;
}
nsresult nsMsgResultElement::GetValue (nsMsgSearchAttribute attrib, nsMsgSearchValue **outValue) const
{
nsresult err = NS_OK;
nsMsgSearchValue *value = NULL;
*outValue = NULL;
for (int i = 0; i < m_valueList.Count() && err != NS_OK; i++)
{
value = m_valueList.ElementAt(i);
if (attrib == value->attribute)
{
*outValue = new nsMsgSearchValue;
if (*outValue)
{
err = AssignValues (value, *outValue);
err = NS_OK;
}
else
err = NS_ERROR_OUT_OF_MEMORY;
}
}
#ifdef HAVE_SEARCH_PORT
// No need to store the folderInfo separately; we can always get it if/when
// we need it. This code is to support "view thread context" in the search dialog
if (SearchError_ScopeAgreement == err && attrib == nsMsgSearchAttrib::FolderInfo)
{
nsMsgFolderInfo *targetFolder = m_adapter->FindTargetFolder (this);
if (targetFolder)
{
*outValue = new nsMsgSearchValue;
if (*outValue)
{
(*outValue)->u.folder = targetFolder;
(*outValue)->attribute = nsMsgSearchAttrib::FolderInfo;
err = NS_OK;
}
}
}
#endif
return err;
}
const nsMsgSearchValue *nsMsgResultElement::GetValueRef (nsMsgSearchAttribute attrib) const
{
nsMsgSearchValue *value = NULL;
for (int i = 0; i < m_valueList.Count(); i++)
{
value = m_valueList.ElementAt(i);
if (attrib == value->attribute)
return value;
}
return NULL;
}
nsresult nsMsgResultElement::GetPrettyName (nsMsgSearchValue **value)
{
nsresult err = GetValue (nsMsgSearchAttrib::Location, value);
#ifdef HAVE_SEARCH_PORT
if (NS_OK == err)
{
nsMsgFolderInfo *folder = m_adapter->m_scope->m_folder;
nsMsgNewsHost *host = NULL;
if (folder)
{
// Find the news host because only the host knows whether pretty
// names are supported.
if (FOLDER_CONTAINERONLY == folder->GetType())
host = ((nsMsgNewsFolderInfoContainer*) folder)->GetHost();
else if (folder->IsNews())
host = folder->GetNewsFolderInfo()->GetHost();
// Ask the host whether it knows pretty names. It isn't strictly
// necessary to avoid calling folder->GetPrettiestName() since it
// does the right thing. But we do have to find the folder from the host.
if (host && host->QueryExtension ("LISTPNAMES"))
{
folder = host->FindGroup ((*value)->u.string);
if (folder)
{
char *tmp = nsCRT::strdup (folder->GetPrettiestName());
if (tmp)
{
XP_FREE ((*value)->u.string);
(*value)->u.string = tmp;
}
}
}
}
}
#endif // HAVE_SEARCH_PORT
return err;
}
int nsMsgResultElement::CompareByFolderInfoPtrs (const void *e1, const void *e2)
{
#ifdef HAVE_SEARCH_PORT
nsMsgResultElement * re1 = *(nsMsgResultElement **) e1;
nsMsgResultElement * re2 = *(nsMsgResultElement **) e2;
// get the src folder for each one
const nsMsgSearchValue * v1 = re1->GetValueRef(attribFolderInfo);
const nsMsgSearchValue * v2 = re2->GetValueRef(attribFolderInfo);
if (!v1 || !v2)
return 0;
return (v1->u.folder - v2->u.folder);
#else
return -1;
#endif // HAVE_SEARCH_PORT
}
int nsMsgResultElement::Compare (const void *e1, const void *e2)
{
int ret = 0;
#ifdef HAVE_SEARCH_PORT
// Bad karma to cast away const, but they're my objects anway.
// Maybe if we go through and const everything this should be a const ptr.
nsMsgResultElement *re1 = *(nsMsgResultElement**) e1;
nsMsgResultElement *re2 = *(nsMsgResultElement**) e2;
NS_ASSERTION(re1->IsValid(), "invalid result element1 in resultElement::Compare");
NS_ASSERTION(re2->IsValid(), "invalid result element2 in resultElement::Compare");
nsMsgSearchAttribute attrib = re1->m_adapter->m_scope->m_frame->m_sortAttribute;
const nsMsgSearchValue *v1 = re1->GetValueRef (attrib);
const nsMsgSearchValue *v2 = re2->GetValueRef (attrib);
if (!v1 || !v2)
return ret; // search result doesn't contain the attrib we want to sort on
switch (attrib)
{
case nsMsgSearchAttrib::Date:
{
// on Win 3.1, the requisite 'int' return type is a short, so use a
// real time_t for comparison
time_t date = v1->u.date - v2->u.date;
if (date)
ret = ((long)date) < 0 ? -1 : 1;
else
ret = 0;
}
break;
case nsMsgSearchAttrib::Priority:
ret = v1->u.priority - v2->u.priority;
break;
case nsMsgSearchAttrib::MsgStatus:
{
// Here's a totally arbitrary sorting protocol for msg status
uint32 s1, s2;
s1 = v1->u.msgStatus & ~MSG_FLAG_REPLIED;
s2 = v2->u.msgStatus & ~MSG_FLAG_REPLIED;
if (s1 || s2)
ret = s1 - s2;
else
{
s1 = v1->u.msgStatus & ~MSG_FLAG_FORWARDED;
s2 = v2->u.msgStatus & ~MSG_FLAG_FORWARDED;
if (s1 || s2)
ret = s1 - s2;
else
{
s1 = v1->u.msgStatus & ~MSG_FLAG_READ;
s2 = v2->u.msgStatus & ~MSG_FLAG_READ;
if (s1 || s2)
ret = s1 - s2;
else
// statuses don't contain any flags we're interested in,
// so they're equal as far as we care
ret = 0;
}
}
}
break;
default:
if (attrib == nsMsgSearchAttrib::Subject)
{
// Special case for subjects, so "Re:foo" sorts under 'f' not 'r'
const char *s1 = v1->u.string;
const char *s2 = v2->u.string;
msg_StripRE (&s1, NULL);
msg_StripRE (&s2, NULL);
ret = PL_strcasecomp (s1, s2);
}
else
ret = strcasecomp (v1->u.string, v2->u.string);
}
// ### need different hack for this.
// qsort's default sort order is ascending, so in order to get descending
// behavior, we'll tell qsort a lie and reverse the comparison order.
if (re1->m_adapter->m_scope->m_frame->m_descending && ret != 0)
if (ret < 0)
ret = 1;
else
ret = -1;
// <0 --> e1 less than e2
// 0 --> e1 equal to e2
// >0 --> e1 greater than e2
#endif
return ret;
}
#ifdef HAVE_SEARCH_PORT
MWContextType nsMsgResultElement::GetContextType ()
{
MWContextType type=(MWContextType)0;
switch (m_adapter->m_scope->m_attribute)
{
case nsMsgSearchScopeMailFolder:
type = MWContextMailMsg;
break;
case nsMsgSearchScopeOfflineNewsgroup: // added by mscott could be bug fix...
case nsMsgSearchScopeNewsgroup:
case nsMsgSearchScopeAllSearchableGroups:
type = MWContextNewsMsg;
break;
case nsMsgSearchScopeLdapDirectory:
type = MWContextBrowser;
break;
default:
NS_ASSERTION(PR_FALSE, "invalid scope"); // should never happen
}
return type;
}
#endif
nsresult nsMsgResultElement::Open (void *window)
{
#ifdef HAVE_SEARCH_PORT
// ###phil this is a little ugly, but I'm not inclined to invest more in it
// until the libnet rework is done and I know what kind of context we'll end up with
if (window)
{
if (m_adapter->m_scope->m_attribute != nsMsgSearchScopeLdapDirectory)
{
msgPane = (MSG_MessagePane *) window;
PR_ASSERT (MSG_MESSAGEPANE == msgPane->GetPaneType());
return m_adapter->OpenResultElement (msgPane, this);
}
else
{
context = (MWContext*) window;
PR_ASSERT (MWContextBrowser == context->type);
msg_SearchLdap *thisAdapter = (msg_SearchLdap*) m_adapter;
return thisAdapter->OpenResultElement (context, this);
}
}
#endif
return NS_ERROR_NULL_POINTER;
}