mailnews/base/search/src/nsMsgSearchTerm.cpp
author Kent James <kent@caspia.com>
Tue, 10 Mar 2009 09:24:42 +0000
changeset 2170 c55d44c0a936
parent 2134 6bdc9d138c65
child 2859 d97f55ec5ca0
permissions -rw-r--r--
Bug 473091 - "Saved Search doesnt allow "Tags isn't empty"" [r=kent sr=bienvenu]
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla 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/MPL/
 *
 * 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 the Initial Developer are Copyright (C) 1999
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Seth Spitzer <sspitzer@netscape.com>
 *   Jungshik Shin <jshin@mailaps.org>
 *   David Bienvenu <bienvenu@nventure.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include "msgCore.h"
#include "prmem.h"
#include "nsMsgSearchCore.h"
#include "nsIMsgSearchSession.h"
#include "nsMsgUtils.h"
#include "nsIMsgDatabase.h"
#include "nsIMsgHdr.h"
#include "nsMsgSearchTerm.h"
#include "nsMsgSearchScopeTerm.h"
#include "nsMsgBodyHandler.h"
#include "nsMsgResultElement.h"
#include "nsIMsgImapMailFolder.h"
#include "nsMsgSearchImap.h"
#include "nsMsgLocalSearch.h"
#include "nsMsgSearchNews.h"
#include "nsMsgSearchValue.h"
#include "nsMsgI18N.h"
#include "nsIMimeConverter.h"
#include "nsMsgMimeCID.h"
#include "nsTime.h"
#include "nsIPrefBranch.h"
#include "nsIPrefService.h"
#include "nsIMsgFilterPlugin.h"
#include "nsILocalFile.h"
#include "nsIRDFService.h"
#include "nsISupportsObsolete.h"
#include "nsNetCID.h"
#include "nsIFileStreams.h"
#include "nsUnicharUtils.h"
#include "nsIAbCard.h"
#include "nsServiceManagerUtils.h"
#include "nsComponentManagerUtils.h"
#include "nsMemory.h"
#include <ctype.h>
#include "nsMsgBaseCID.h"
#include "nsIMsgTagService.h"
#include "nsMsgMessageFlags.h"

//---------------------------------------------------------------------------
// nsMsgSearchTerm specifies one criterion, e.g. name contains phil
//---------------------------------------------------------------------------


//-----------------------------------------------------------------------------
//-------------------- Implementation of nsMsgSearchTerm -----------------------
//-----------------------------------------------------------------------------
#define MAILNEWS_CUSTOM_HEADERS "mailnews.customHeaders"

typedef struct
{
  nsMsgSearchAttribValue  attrib;
  const char      *attribName;
} nsMsgSearchAttribEntry;

nsMsgSearchAttribEntry SearchAttribEntryTable[] =
{
    {nsMsgSearchAttrib::Subject,    "subject"},
    {nsMsgSearchAttrib::Sender,     "from"},
    {nsMsgSearchAttrib::Body,       "body"},
    {nsMsgSearchAttrib::Date,       "date"},
    {nsMsgSearchAttrib::Priority,   "priority"},
    {nsMsgSearchAttrib::MsgStatus,  "status"},
    {nsMsgSearchAttrib::To,         "to"},
    {nsMsgSearchAttrib::CC,         "cc"},
    {nsMsgSearchAttrib::ToOrCC,     "to or cc"},
    {nsMsgSearchAttrib::AllAddresses, "all addresses"},
    {nsMsgSearchAttrib::AgeInDays,  "age in days"},
    {nsMsgSearchAttrib::Label,      "label"},
    {nsMsgSearchAttrib::Keywords,   "tag"},
    {nsMsgSearchAttrib::Size,       "size"},
    // this used to be nsMsgSearchAttrib::SenderInAddressBook
    // we used to have two Sender menuitems
    // for backward compatability, we can still parse
    // the old style.  see bug #179803
    {nsMsgSearchAttrib::Sender,     "from in ab"},
    {nsMsgSearchAttrib::JunkStatus, "junk status"},
    {nsMsgSearchAttrib::JunkPercent, "junk percent"},
    {nsMsgSearchAttrib::JunkScoreOrigin, "junk score origin"},
    {nsMsgSearchAttrib::HasAttachmentStatus, "has attachment status"},
};

// 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)
{
  NS_ENSURE_ARG_POINTER(string);
  NS_ENSURE_ARG_POINTER(attrib);

  PRBool found = PR_FALSE;
  // custom headers have a leading quote
  if (*string != '"')
  {
    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;
      }
    }
  }
  else // remove the leading quote
    string++;

  if (!found)
  {
    nsresult rv;
    PRBool goodHdr;
    IsRFC822HeaderFieldName(string, &goodHdr);
    if (!goodHdr)
      return NS_MSG_INVALID_CUSTOM_HEADER;
    //49 is for showing customize... in ui, headers start from 50 onwards up until 99.
    *attrib = nsMsgSearchAttrib::OtherHeader+1;

    nsCOMPtr<nsIPrefService> prefService = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);

    nsCOMPtr<nsIPrefBranch> prefBranch;
    rv = prefService->GetBranch(nsnull, getter_AddRefs(prefBranch));
    NS_ENSURE_SUCCESS(rv, rv);

    nsCString headers;
    prefBranch->GetCharPref(MAILNEWS_CUSTOM_HEADERS, getter_Copies(headers));

    if (!headers.IsEmpty())
    {
      nsCAutoString hdrStr(headers);
      hdrStr.StripWhitespace();  //remove whitespace before parsing

      char *newStr= hdrStr.BeginWriting();
      char *token = NS_strtok(":", &newStr);
      PRUint32 i=0;
      while (token)
      {
        if (PL_strcasecmp(token, string) == 0)
        {
          *attrib += i; //we found custom header in the pref
          found = PR_TRUE;
          break;
        }
        token = NS_strtok(":", &newStr);
        i++;
      }
    }
  }
  // If we didn't find the header in MAILNEWS_CUSTOM_HEADERS, we're
  // going to return NS_OK and an attrib of nsMsgSearchAttrib::OtherHeader+1.
  // in case it's a client side spam filter description filter,
  // which doesn't add its headers to mailnews.customMailHeaders.
  // We've already checked that it's a valid header and returned
  // an error if so.

  return NS_OK;
}

nsresult NS_MsgGetStringForAttribute(PRInt16 attrib, const char **string)
{
  NS_ENSURE_ARG_POINTER(string);

  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;
    }
  }
  if (!found)
    *string = '\0'; // don't leave the string uninitialized

  // we no longer return invalid attribute. If we cannot find the string in the table,
  // then it is an arbitrary header. Return success regardless if found or not
  return NS_OK;
}

typedef struct
{
  nsMsgSearchOpValue  op;
  const char      *opName;
} nsMsgSearchOperatorEntry;

nsMsgSearchOperatorEntry SearchOperatorEntryTable[] =
{
  {nsMsgSearchOp::Contains, "contains"},
  {nsMsgSearchOp::DoesntContain,"doesn't contain"},
  {nsMsgSearchOp::Is,"is"},
  {nsMsgSearchOp::Isnt,  "isn't"},
  {nsMsgSearchOp::IsEmpty, "is empty"},
  {nsMsgSearchOp::IsntEmpty, "isn't empty"},
  {nsMsgSearchOp::IsBefore, "is before"},
  {nsMsgSearchOp::IsAfter, "is after"},
  {nsMsgSearchOp::IsHigherThan, "is higher than"},
  {nsMsgSearchOp::IsLowerThan, "is lower than"},
  {nsMsgSearchOp::BeginsWith, "begins with"},
  {nsMsgSearchOp::EndsWith, "ends with"},
  {nsMsgSearchOp::IsInAB, "is in ab"},
  {nsMsgSearchOp::IsntInAB, "isn't in ab"},
  {nsMsgSearchOp::IsGreaterThan, "is greater than"},
  {nsMsgSearchOp::IsLessThan, "is less than"}
};

nsresult NS_MsgGetOperatorFromString(const char *string, PRInt16 *op)
{
  NS_ENSURE_ARG_POINTER(string);
  NS_ENSURE_ARG_POINTER(op);

  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)
{
  NS_ENSURE_ARG_POINTER(string);

  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)
{
  const char *tmpOutName = NULL;
#define MSG_STATUS_MASK (nsMsgMessageFlags::Read | nsMsgMessageFlags::Replied |\
  nsMsgMessageFlags::Forwarded | nsMsgMessageFlags::New | nsMsgMessageFlags::Marked)
  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 & nsMsgMessageFlags::New)
    maskOut = nsMsgMessageFlags::New;
  if (maskOut & nsMsgMessageFlags::Replied &&
      maskOut & nsMsgMessageFlags::Forwarded)
    maskOut = nsMsgMessageFlags::Replied | nsMsgMessageFlags::Forwarded;
  else if (maskOut & nsMsgMessageFlags::Forwarded)
    maskOut = nsMsgMessageFlags::Forwarded;
  else if (maskOut & nsMsgMessageFlags::Replied)
    maskOut = nsMsgMessageFlags::Replied;

  switch (maskOut)
  {
  case nsMsgMessageFlags::Read:
    tmpOutName = "read";
    break;
  case nsMsgMessageFlags::Replied:
    tmpOutName = "replied";
    break;
  case nsMsgMessageFlags::Forwarded:
    tmpOutName = "forwarded";
    break;
  case nsMsgMessageFlags::Forwarded | nsMsgMessageFlags::Replied:
    tmpOutName = "replied and forwarded";
    break;
  case nsMsgMessageFlags::New:
    tmpOutName = "new";
    break;
  case nsMsgMessageFlags::Marked:
    tmpOutName = "flagged";
    break;
  default:
    // This is fine, status may be "unread" for example
    break;
  }

  if (tmpOutName)
    *outName = tmpOutName;
}


PRInt32 NS_MsgGetStatusValueFromName(char *name)
{
  if (!strcmp("read", name))
    return nsMsgMessageFlags::Read;
  if (!strcmp("replied", name))
    return nsMsgMessageFlags::Replied;
  if (!strcmp("forwarded", name))
    return nsMsgMessageFlags::Forwarded;
  if (!strcmp("replied and forwarded", name))
    return nsMsgMessageFlags::Forwarded | nsMsgMessageFlags::Replied;
  if (!strcmp("new", name))
    return nsMsgMessageFlags::New;
  if (!strcmp("flagged", name))
    return nsMsgMessageFlags::Marked;
  return 0;
}


// Needed for DeStream method.
nsMsgSearchTerm::nsMsgSearchTerm()
{
    // initialize this to zero
    m_value.string=nsnull;
    m_value.attribute=0;
    m_value.u.priority=0;
    m_attribute = nsMsgSearchAttrib::Default;
    mBeginsGrouping = PR_FALSE;
    mEndsGrouping = PR_FALSE;
    m_matchAll = PR_FALSE;
}

nsMsgSearchTerm::nsMsgSearchTerm (
                                  nsMsgSearchAttribValue attrib,
                                  nsMsgSearchOpValue op,
                                  nsIMsgSearchValue *val,
                                  nsMsgSearchBooleanOperator boolOp,
                                  const char * arbitraryHeader)
{
  m_operator = op;
  m_attribute = attrib;
  m_booleanOp = boolOp;
  if (attrib > nsMsgSearchAttrib::OtherHeader  && attrib < nsMsgSearchAttrib::kNumMsgSearchAttributes && arbitraryHeader)
  {
    m_arbitraryHeader = arbitraryHeader;
    ToLowerCaseExceptSpecials(m_arbitraryHeader);
  }
  nsMsgResultElement::AssignValues (val, &m_value);
  m_matchAll = PR_FALSE;
}



nsMsgSearchTerm::~nsMsgSearchTerm ()
{
  if (IS_STRING_ATTRIBUTE (m_attribute) && m_value.string)
    NS_Free(m_value.string);
}

NS_IMPL_ISUPPORTS1(nsMsgSearchTerm, nsIMsgSearchTerm)


// Perhaps we could find a better place for this?
// Caller needs to free.
/* static */char *nsMsgSearchTerm::EscapeQuotesInStr(const char *str)
{
  int  numQuotes = 0;
  for (const char *strPtr = str; *strPtr; strPtr++)
    if (*strPtr == '"')
      numQuotes++;
    int escapedStrLen = PL_strlen(str) + numQuotes;
    char  *escapedStr = (char *) PR_Malloc(escapedStrLen + 1);
    if (escapedStr)
    {
      char *destPtr;
      for (destPtr = escapedStr; *str; str++)
      {
        if (*str == '"')
          *destPtr++ = '\\';
        *destPtr++ = *str;
      }
      *destPtr = '\0';
    }
    return escapedStr;
}


nsresult nsMsgSearchTerm::OutputValue(nsCString &outputStr)
{
  if (IS_STRING_ATTRIBUTE(m_attribute) && m_value.string)
  {
    PRBool  quoteVal = PR_FALSE;
    // need to quote strings with ')' and strings starting with '"' or ' '
    // filter code will escape quotes
    if (PL_strchr(m_value.string, ')') ||
      (m_value.string[0] == ' ') ||
      (m_value.string[0] == '"'))
    {
      quoteVal = PR_TRUE;
      outputStr += "\"";
    }
    if (PL_strchr(m_value.string, '"'))
    {
      char *escapedString = nsMsgSearchTerm::EscapeQuotesInStr(m_value.string);
      if (escapedString)
      {
        outputStr += escapedString;
        PR_Free(escapedString);
      }

    }
    else
    {
      outputStr += m_value.string;
    }
    if (quoteVal)
      outputStr += "\"";
  }
  else
  {
    switch (m_attribute)
    {
    case nsMsgSearchAttrib::Date:
      {
        PRExplodedTime exploded;
        PR_ExplodeTime(m_value.u.date, PR_LocalTimeParameters, &exploded);

        // wow, so tm_mon is 0 based, tm_mday is 1 based.
        char dateBuf[100];
        PR_FormatTimeUSEnglish (dateBuf, sizeof(dateBuf), "%d-%b-%Y", &exploded);
        outputStr += dateBuf;
        break;
      }
    case nsMsgSearchAttrib::AgeInDays:
      {
        outputStr.AppendInt(m_value.u.age);
        break;
      }
    case nsMsgSearchAttrib::Label:
      {
        outputStr.AppendInt(m_value.u.label);
        break;
      }
    case nsMsgSearchAttrib::JunkStatus:
      {
        outputStr.AppendInt(m_value.u.junkStatus); // only if we write to disk, right?
        break;
      }
    case nsMsgSearchAttrib::JunkPercent:
      {
        outputStr.AppendInt(m_value.u.junkPercent);
        break;
      }
    case nsMsgSearchAttrib::MsgStatus:
      {
        nsCAutoString status;
        NS_MsgGetUntranslatedStatusName (m_value.u.msgStatus, &status);
        outputStr += status;
        break;
      }
    case nsMsgSearchAttrib::Priority:
      {
        nsCAutoString priority;
        NS_MsgGetUntranslatedPriorityName(m_value.u.priority, priority);
        outputStr += priority;
        break;
      }
    case nsMsgSearchAttrib::HasAttachmentStatus:
      {
        outputStr.Append("true");  // don't need anything here, really
        break;
      }
    case nsMsgSearchAttrib::Size:
      {
        outputStr.AppendInt(m_value.u.size);
        break;
      }
    default:
      NS_ASSERTION(PR_FALSE, "trying to output invalid attribute");
      break;
    }
  }
  return NS_OK;
}

NS_IMETHODIMP nsMsgSearchTerm::GetTermAsString (nsACString &outStream)
{
  const char *operatorStr;
  nsCAutoString  outputStr;
  nsresult  ret;

  if (m_matchAll)
  {
    outStream = "ALL";
    return NS_OK;
  }

  if (m_attribute > nsMsgSearchAttrib::OtherHeader && m_attribute < nsMsgSearchAttrib::kNumMsgSearchAttributes)  // if arbitrary header, use it instead!
  {
    outputStr = "\"";
    outputStr += m_arbitraryHeader;
    outputStr += "\"";
  }
  else {
    const char *attrib;
    ret = NS_MsgGetStringForAttribute(m_attribute, &attrib);
    if (ret != NS_OK)
      return ret;
    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 (isspace(*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.string = (char *) PR_Malloc(valueLen + 1);
    PL_strncpy(m_value.string, inStream, valueLen + 1);
    m_value.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;
    case nsMsgSearchAttrib::AgeInDays:
      m_value.u.age = atoi(inStream);
      break;
    case nsMsgSearchAttrib::Label:
      m_value.u.label = atoi(inStream);
      break;
    case nsMsgSearchAttrib::JunkStatus:
      m_value.u.junkStatus = atoi(inStream); // only if we read from disk, right?
      break;
    case nsMsgSearchAttrib::JunkPercent:
      m_value.u.junkPercent = atoi(inStream);
      break;
    case nsMsgSearchAttrib::HasAttachmentStatus:
      m_value.u.msgStatus = nsMsgMessageFlags::Attachment;
      break; // this should always be true.
    case nsMsgSearchAttrib::Size:
      m_value.u.size = atoi(inStream);
      break;
    default:
      NS_ASSERTION(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.
nsresult
nsMsgSearchTerm::ParseOperator(char *inStream, nsMsgSearchOpValue *value)
{
  NS_ENSURE_ARG_POINTER(value);
  PRInt16 operatorVal;
  while (isspace(*inStream))
    inStream++;

  char *commaSep = PL_strchr(inStream, ',');

  if (commaSep)
    *commaSep = '\0';

  nsresult err = NS_MsgGetOperatorFromString(inStream, &operatorVal);
  *value = (nsMsgSearchOpValue) operatorVal;
  return err;
}

// find the attribute code for this comma-delimited attribute.
nsresult
nsMsgSearchTerm::ParseAttribute(char *inStream, nsMsgSearchAttribValue *attrib)
{
    while (isspace(*inStream))
        inStream++;

    // if we are dealing with an arbitrary header, it will be quoted....
    // it seems like a kludge, but to distinguish arbitrary headers from
    // standard headers with the same name, like "Date", we'll use the
    // presence of the quote to recognize arbitrary headers. We leave the
    // leading quote as a flag, but remove the trailing quote.
    PRBool quoteVal = PR_FALSE;
    if (*inStream == '"')
        quoteVal = PR_TRUE;

    // arbitrary headers are quoted. Skip first character, which will be the
    // first quote for arbitrary headers
    char *separator = strchr(inStream + 1, quoteVal ? '"' : ',');

    if (separator)
        *separator = '\0';

    PRInt16 attributeVal;
    nsresult rv = NS_MsgGetAttributeFromString(inStream, &attributeVal);
    NS_ENSURE_SUCCESS(rv, rv);

    *attrib = (nsMsgSearchAttribValue) attributeVal;

    if (*attrib > nsMsgSearchAttrib::OtherHeader && *attrib < nsMsgSearchAttrib::kNumMsgSearchAttributes)  // if we are dealing with an arbitrary header....
    {
      m_arbitraryHeader = inStream + 1; // remove the leading quote
      ToLowerCaseExceptSpecials(m_arbitraryHeader);
    }
    return rv;
}

// De stream one search term. If the condition looks like
// condition = "(to or cc, contains, r-thompson) AND (body, doesn't contain, fred)"
// This routine should get called twice, the first time
// with "to or cc, contains, r-thompson", the second time with
// "body, doesn't contain, fred"

nsresult nsMsgSearchTerm::DeStreamNew (char *inStream, PRInt16 /*length*/)
{
  if (!strcmp(inStream, "ALL"))
  {
    m_matchAll = PR_TRUE;
    return NS_OK;
  }
  char *commaSep = PL_strchr(inStream, ',');
  nsresult rv = ParseAttribute(inStream, &m_attribute);  // will allocate space for arbitrary header if necessary
  NS_ENSURE_SUCCESS(rv, rv);
  if (!commaSep)
    return NS_ERROR_INVALID_ARG;
  char *secondCommaSep = PL_strchr(commaSep + 1, ',');
  if (commaSep)
    rv = ParseOperator(commaSep + 1, &m_operator);
  NS_ENSURE_SUCCESS(rv, rv);
  // convert label filters and saved searches to keyword equivalents
  if (secondCommaSep)
    ParseValue(secondCommaSep + 1);
  if (m_attribute == nsMsgSearchAttrib::Label)
  {
    nsCAutoString keyword("$label");
    m_value.attribute = m_attribute = nsMsgSearchAttrib::Keywords;
    keyword.Append('0' + m_value.u.label);
    m_value.string = PL_strdup(keyword.get());
  }
  return NS_OK;
}


// Looks in the MessageDB for the user specified arbitrary header, if it finds the header, it then looks for a match against
// the value for the header.
nsresult nsMsgSearchTerm::MatchArbitraryHeader (nsIMsgSearchScopeTerm *scope,
                                                PRUint32 offset,
                                                PRUint32 length /* in lines*/,
                                                const char *charset,
                                                PRBool charsetOverride,
                                                nsIMsgDBHdr *msg,
                                                nsIMsgDatabase* db,
                                                const char * headers,
                                                PRUint32 headersSize,
                                                PRBool ForFiltering,
                                                PRBool *pResult)
{
  NS_ENSURE_ARG_POINTER(pResult);
  *pResult = PR_FALSE;
  nsresult err = NS_OK;
  PRBool result;

  GetMatchAllBeforeDeciding(&result);

  nsCString dbHdrValue;
  msg->GetStringProperty(m_arbitraryHeader.get(), getter_Copies(dbHdrValue));
  if (!dbHdrValue.IsEmpty())
  {
    PRBool result2;
    err = MatchRfc2047String(dbHdrValue.get(), charset, charsetOverride, &result2);  // match value with the other info...
    if (result != result2) // if we found a match
      result = result2;
    *pResult = result;
    return err;
  }
  nsMsgBodyHandler * bodyHandler = new nsMsgBodyHandler (scope, offset,length, msg, db, headers, headersSize, ForFiltering);
  if (!bodyHandler)
    return NS_ERROR_OUT_OF_MEMORY;

  bodyHandler->SetStripHeaders (PR_FALSE);


  nsCAutoString buf;
  nsCAutoString curMsgHeader;
  PRBool searchingHeaders = PR_TRUE;
  while (searchingHeaders && (bodyHandler->GetNextLine(buf) >=0))
  {
    char * buf_end = (char *) (buf.get() + buf.Length());
    int headerLength = m_arbitraryHeader.Length();
    PRBool isContinuationHeader = NS_IsAsciiWhitespace(buf.CharAt(0));
    // this handles wrapped header lines, which start with whitespace.
    // If the line starts with whitespace, then we use the current header.
    if (!isContinuationHeader)
    {
      PRUint32 colonPos = buf.FindChar(':');
      curMsgHeader = StringHead(buf, colonPos);
    }

#ifdef MOZILLA_INTERNAL_API
    if (curMsgHeader.Equals(m_arbitraryHeader, nsCaseInsensitiveCStringComparator()))
#else
    if (curMsgHeader.Equals(m_arbitraryHeader, CaseInsensitiveCompare))
#endif
    {
      // value occurs after the header name or whitespace continuation char.
      const char * headerValue = buf.get() + (isContinuationHeader ? 1 : headerLength);
      if (headerValue < buf_end && headerValue[0] == ':')  // + 1 to account for the colon which is MANDATORY
        headerValue++;

      // strip leading white space
      while (headerValue < buf_end && isspace(*headerValue))
        headerValue++; // advance to next character

      // strip trailing white space
      char * end = buf_end - 1;
      while (end > headerValue && isspace(*end)) // while we haven't gone back past the start and we are white space....
      {
        *end = '\0';  // eat up the white space
        end--;      // move back and examine the previous character....
      }

      if (headerValue < buf_end && *headerValue) // make sure buf has info besides just the header
      {
        PRBool result2;
        err = MatchRfc2047String(headerValue, charset, charsetOverride, &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;
  *pResult = result;
  return err;
}

nsresult nsMsgSearchTerm::MatchBody (nsIMsgSearchScopeTerm *scope, PRUint32 offset, PRUint32 length /*in lines*/, const char *folderCharset,
                                      nsIMsgDBHdr *msg, nsIMsgDatabase* db, PRBool *pResult)
{
  NS_ENSURE_ARG_POINTER(pResult);
  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". ### Since length is in lines, this is not quite right.
  if ((length > 0) && (m_operator == nsMsgSearchOp::Is || m_operator == nsMsgSearchOp::Isnt))
    length = PL_strlen (m_value.string);

  nsMsgBodyHandler * bodyHan  = new nsMsgBodyHandler (scope, offset, length, msg, db);
  if (!bodyHan)
    return NS_ERROR_OUT_OF_MEMORY;

  nsCAutoString buf;
  PRBool endOfFile = PR_FALSE;  // if retValue == 0, we've hit the end of the file
  uint32 lines = 0;

  // Change the sense of the loop so we don't bail out prematurely
  // on negative terms. i.e. opDoesntContain must look at all lines
  PRBool boolContinueLoop;
  GetMatchAllBeforeDeciding(&boolContinueLoop);
  result = boolContinueLoop;

  // If there's a '=' in the search term, then we're not going to do
  // quoted printable decoding. Otherwise we assume everything is
  // quoted printable. Obviously everything isn't quoted printable, but
  // since we don't have a MIME parser handy, and we want to err on the
  // side of too many hits rather than not enough, we'll assume in that
  // general direction. Blech. ### FIX ME
  // bug fix #314637: for stateful charsets like ISO-2022-JP, we don't
  // want to decode quoted printable since it contains '='.
  PRBool isQuotedPrintable = !nsMsgI18Nstateful_charset(folderCharset) &&
    (PL_strchr (m_value.string, '=') == nsnull);

  nsCString compare;
  while (!endOfFile && result == boolContinueLoop)
  {
    if (bodyHan->GetNextLine(buf) >= 0)
    {
      PRBool softLineBreak = PR_FALSE;
      // Do in-place decoding of quoted printable
      if (isQuotedPrintable)
      {
        softLineBreak = StringEndsWith(buf, NS_LITERAL_CSTRING("="));
        MsgStripQuotedPrintable ((unsigned char*)buf.get());
        // in case the string shrunk, reset the length. If soft line break,
        // chop off the last char as well.
        buf.SetLength(strlen(buf.get()) - (softLineBreak ? 1 : 0));
      }
      compare.Append(buf);
      // If this line ends with a soft line break, loop around
      // and get the next line before looking for the search string.
      // This assumes the message can't end on a QP soft-line break.
      // That seems like a pretty safe assumption.
      if (softLineBreak)
        continue;
      if (!compare.IsEmpty())
      {
        char startChar = (char) compare.CharAt(0);
        if (startChar != '\r' && startChar != '\n')
        {
          err = MatchString (compare.get(), folderCharset, &result);
          lines++;
        }
        compare.Truncate();
      }
    }
    else
      endOfFile = PR_TRUE;
  }
#ifdef DO_I18N
  if(conv)
    INTL_DestroyCharCodeConverter(conv);
#endif
  delete bodyHan;
  *pResult = result;
  return err;
}

nsresult nsMsgSearchTerm::InitializeAddressBook()
{
  // the search attribute value has the URI for the address book we need to load.
  // we need both the database and the directory.
  nsresult rv = NS_OK;

  if (mDirectory)
  {
    nsCString uri;
    rv = mDirectory->GetURI(uri);
    NS_ENSURE_SUCCESS(rv, rv);

    if (!uri.Equals(m_value.string))
      // clear out the directory....we are no longer pointing to the right one
      mDirectory = nsnull;
  }
  if (!mDirectory)
  {
    nsCOMPtr <nsIRDFService> rdfService = do_GetService("@mozilla.org/rdf/rdf-service;1",&rv);
    NS_ENSURE_SUCCESS(rv, rv);

    nsCOMPtr <nsIRDFResource> resource;
    rv = rdfService->GetResource(nsDependentCString(m_value.string), getter_AddRefs(resource));
    NS_ENSURE_SUCCESS(rv, rv);

    mDirectory = do_QueryInterface(resource, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  return NS_OK;
}

nsresult nsMsgSearchTerm::MatchInAddressBook(const char * aAddress, PRBool *pResult)
{
  nsresult rv = InitializeAddressBook();
  *pResult = PR_FALSE;

  // Some junkmails have empty From: fields.
  if (aAddress == NULL || strlen(aAddress) == 0)
    return rv;

  if (mDirectory)
  {
    nsIAbCard* cardForAddress = nsnull;
    rv = mDirectory->CardForEmailAddress(nsDependentCString(aAddress),
                                         &cardForAddress);
    if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED)
      return rv;
    if ((m_operator == nsMsgSearchOp::IsInAB && cardForAddress) || (m_operator == nsMsgSearchOp::IsntInAB && !cardForAddress))
      *pResult = PR_TRUE;
    NS_IF_RELEASE(cardForAddress);
  }

  return rv;
}

// *pResult is PR_FALSE when strings don't match, PR_TRUE if they do.
nsresult nsMsgSearchTerm::MatchRfc2047String (const char * rfc2047string,
                                       const char *charset,
                                       PRBool charsetOverride,
                                       PRBool *pResult)
{
  NS_ENSURE_ARG_POINTER(pResult);
  NS_ENSURE_ARG_POINTER(rfc2047string);

    nsCOMPtr<nsIMimeConverter> mimeConverter = do_GetService(NS_MIME_CONVERTER_CONTRACTID);
  char *stringToMatch = 0;
    nsresult res = mimeConverter->DecodeMimeHeaderToCharPtr(
        rfc2047string, charset, charsetOverride, PR_FALSE, &stringToMatch);

    if ( m_operator == nsMsgSearchOp::IsInAB ||
         m_operator == nsMsgSearchOp::IsntInAB)
    {
      res = MatchInAddressBook(stringToMatch ? stringToMatch : rfc2047string, pResult);
    }
    else
    res = MatchString(stringToMatch ? stringToMatch : rfc2047string,
                      nsnull, pResult);

    PR_Free(stringToMatch);

  return res;
}

// *pResult is PR_FALSE when strings don't match, PR_TRUE if they do.
nsresult nsMsgSearchTerm::MatchString (const char *stringToMatch,
                                       const char *charset,
                                       PRBool *pResult)
{
  NS_ENSURE_ARG_POINTER(pResult);
  PRBool result = PR_FALSE;

  nsresult err = NS_OK;
  nsAutoString utf16StrToMatch;
  nsAutoString needle;

  // Save some performance for opIsEmpty / opIsntEmpty
  if(nsMsgSearchOp::IsEmpty != m_operator && nsMsgSearchOp::IsntEmpty != m_operator)
  {
    NS_ASSERTION(MsgIsUTF8(nsDependentCString(m_value.string)),
                 "m_value.string is not UTF-8");
    CopyUTF8toUTF16(nsDependentCString(m_value.string), needle);

    if (charset != nsnull)
    {
      ConvertToUnicode(charset, stringToMatch ? stringToMatch : "",
                       utf16StrToMatch);
    }
    else {
      NS_ASSERTION(MsgIsUTF8(nsDependentCString(stringToMatch)),
                   "stringToMatch is not UTF-8");
      CopyUTF8toUTF16(nsDependentCString(stringToMatch), utf16StrToMatch);
    }
  }

  switch (m_operator)
  {
  case nsMsgSearchOp::Contains:
#ifdef MOZILLA_INTERNAL_API
    if (CaseInsensitiveFindInReadable(needle, utf16StrToMatch))
#else
    if (utf16StrToMatch.Find(needle, CaseInsensitiveCompare) != -1);
#endif
      result = PR_TRUE;
    break;
  case nsMsgSearchOp::DoesntContain:
#ifdef MOZILLA_INTERNAL_API
    if (!CaseInsensitiveFindInReadable(needle, utf16StrToMatch))
#else
    if (utf16StrToMatch.Find(needle, CaseInsensitiveCompare) == -1);
#endif
      result = PR_TRUE;
    break;
  case nsMsgSearchOp::Is:
#ifdef MOZILLA_INTERNAL_API
    if(needle.Equals(utf16StrToMatch, nsCaseInsensitiveStringComparator()))
#else
    if(needle.Equals(utf16StrToMatch, CaseInsensitiveCompare))
#endif
      result = PR_TRUE;
    break;
  case nsMsgSearchOp::Isnt:
#ifdef MOZILLA_INTERNAL_API
    if(!needle.Equals(utf16StrToMatch, nsCaseInsensitiveStringComparator()))
#else
    if(!needle.Equals(utf16StrToMatch, CaseInsensitiveCompare))
#endif
      result = PR_TRUE;
    break;
  case nsMsgSearchOp::IsEmpty:
    // For IsEmpty, we didn't copy stringToMatch to utf16StrToMatch.
    if (!PL_strlen(stringToMatch))
      result = PR_TRUE;
    break;
  case nsMsgSearchOp::IsntEmpty:
    // For IsntEmpty, we didn't copy stringToMatch to utf16StrToMatch.
    if (PL_strlen(stringToMatch))
      result = PR_TRUE;
    break;
  case nsMsgSearchOp::BeginsWith:
#ifdef MOZILLA_INTERNAL_API
    if (StringBeginsWith(utf16StrToMatch, needle,
                         nsCaseInsensitiveStringComparator()))
#else
    if (StringBeginsWith(utf16StrToMatch, needle, CaseInsensitiveCompare))
#endif
      result = PR_TRUE;
    break;
  case nsMsgSearchOp::EndsWith:
#ifdef MOZILLA_INTERNAL_API
    if (StringEndsWith(utf16StrToMatch, needle,
                       nsCaseInsensitiveStringComparator()))
#else
    if (StringEndsWith(utf16StrToMatch, needle, CaseInsensitiveCompare))
#endif
      result = PR_TRUE;
    break;
  default:
    NS_ASSERTION(PR_FALSE, "invalid operator matching search results");
  }

  *pResult = result;
  return err;
}

NS_IMETHODIMP nsMsgSearchTerm::GetMatchAllBeforeDeciding (PRBool *aResult)
{
 *aResult = (m_operator == nsMsgSearchOp::DoesntContain || m_operator == nsMsgSearchOp::Isnt);
 return NS_OK;
}

 nsresult nsMsgSearchTerm::MatchRfc822String (const char *string, const char *charset, PRBool charsetOverride, PRBool *pResult)
 {
   NS_ENSURE_ARG_POINTER(pResult);
   *pResult = PR_FALSE;
   PRBool result;
   nsresult err = InitHeaderAddressParser();
   if (NS_FAILED(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;
   GetMatchAllBeforeDeciding(&boolContinueLoop);
   result = boolContinueLoop;

   PRUint32 count;
   nsresult parseErr = m_headerAddressParser->ParseHeaderAddresses(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;
     nsCAutoString walkAddresses;
     PRInt32 namePos = 0;
     PRInt32 addressPos = 0;
     for (PRUint32 i = 0; i < count && result == boolContinueLoop; i++)
     {
       walkNames = names + namePos;
       walkAddresses = addresses + addressPos;
       if ( m_operator == nsMsgSearchOp::IsInAB ||
            m_operator == nsMsgSearchOp::IsntInAB)
       {
         err = MatchRfc2047String (walkAddresses.get(), charset, charsetOverride, &result);
       }
       else
       {
         err = MatchRfc2047String (walkNames.get(), charset, charsetOverride, &result);
         if (boolContinueLoop == result)
           err = MatchRfc2047String (walkAddresses.get(), charset, charsetOverride, &result);
       }

       namePos += walkNames.Length() + 1;
       addressPos += walkAddresses.Length() + 1;
     }

     PR_Free(names);
     PR_Free(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)
{
  NS_ENSURE_ARG_POINTER(pResult);

  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)
{
  NS_ENSURE_ARG_POINTER(pResult);

  PRBool result = PR_FALSE;
  nsresult err = NS_OK;

  PRTime now = PR_Now();
  PRTime cutOffDay;

  PRInt64 microSecondsPerSecond, secondsInDays, microSecondsInDays;

  LL_I2L(microSecondsPerSecond, PR_USEC_PER_SEC);
  LL_I2L(secondsInDays, 60 * 60 * 24 * m_value.u.age);
  LL_MUL(microSecondsInDays, secondsInDays, microSecondsPerSecond);
  LL_SUB(cutOffDay, now, microSecondsInDays); // = now - term->m_value.u.age * 60 * 60 * 24;

  PRBool cutOffDayInTheFuture = LL_CMP(m_value.u.age, <, 0);

  // So now cutOffDay is the PRTime cut-off point.
  // Any msg with a time less than that will be past the age.

  switch (m_operator)
  {
    case nsMsgSearchOp::IsGreaterThan: // is older than, or more in the future
      if ((!cutOffDayInTheFuture && LL_CMP(msgDate, <, cutOffDay)) ||
          (cutOffDayInTheFuture && LL_CMP(msgDate, >, cutOffDay)))
        result = PR_TRUE;
      break;
    case nsMsgSearchOp::IsLessThan: // is younger than, or less in the future
      if ((!cutOffDayInTheFuture && LL_CMP(msgDate, >, cutOffDay)) ||
          (cutOffDayInTheFuture && LL_CMP(msgDate, <, cutOffDay)))
        result = PR_TRUE;
      break;
    case nsMsgSearchOp::Is:
      PRExplodedTime msgDateExploded;
      PRExplodedTime cutOffDayExploded;
      if (NS_SUCCEEDED(GetLocalTimes(msgDate, cutOffDay, msgDateExploded, cutOffDayExploded)))
      {
        if ((msgDateExploded.tm_mday == cutOffDayExploded.tm_mday) &&
            (msgDateExploded.tm_month == cutOffDayExploded.tm_month) &&
            (msgDateExploded.tm_year == cutOffDayExploded.tm_year))
          result = PR_TRUE;
      }
      break;
    default:
      NS_ASSERTION(PR_FALSE, "invalid compare op for msg age");
  }
  *pResult = result;
  return err;
}


nsresult nsMsgSearchTerm::MatchSize (PRUint32 sizeToMatch, PRBool *pResult)
{
  NS_ENSURE_ARG_POINTER(pResult);

  PRBool result = PR_FALSE;
  // We reduce the sizeToMatch rather than supplied size
  // as then we can do an exact match on the displayed value
  // which will be less confusing to the user.
  PRUint32 sizeToMatchKB = sizeToMatch;

  if (sizeToMatchKB < 1024)
    sizeToMatchKB = 1024;

  sizeToMatchKB /= 1024;

  switch (m_operator)
  {
  case nsMsgSearchOp::IsGreaterThan:
    if (sizeToMatchKB > m_value.u.size)
      result = PR_TRUE;
    break;
  case nsMsgSearchOp::IsLessThan:
    if (sizeToMatchKB < m_value.u.size)
      result = PR_TRUE;
    break;
  case nsMsgSearchOp::Is:
    if (sizeToMatchKB == m_value.u.size)
      result = PR_TRUE;
    break;
  default:
    break;
  }
  *pResult = result;
  return NS_OK;
}

nsresult nsMsgSearchTerm::MatchJunkStatus(const char *aJunkScore, PRBool *pResult)
{
  NS_ENSURE_ARG_POINTER(pResult);

  nsMsgJunkStatus junkStatus;
  if (aJunkScore && *aJunkScore) {
      junkStatus = (atoi(aJunkScore) == nsIJunkMailPlugin::IS_SPAM_SCORE) ?
          nsIJunkMailPlugin::JUNK :
          nsIJunkMailPlugin::GOOD;

  }
  else {
    // the in UI, we only show "junk" or "not junk"
    // unknown, or nsIJunkMailPlugin::UNCLASSIFIED is shown as not junk
    // so for the search to work as expected, treat unknown as not junk
    junkStatus = nsIJunkMailPlugin::GOOD;
  }

  nsresult rv = NS_OK;
  PRBool matches = (junkStatus == m_value.u.junkStatus);

  switch (m_operator)
  {
    case nsMsgSearchOp::Is:
      break;
    case nsMsgSearchOp::Isnt:
      matches = !matches;
      break;
    default:
      rv = NS_ERROR_FAILURE;
      NS_ASSERTION(PR_FALSE, "invalid compare op for junk status");
  }

  *pResult = matches;
  return rv;
}

nsresult nsMsgSearchTerm::MatchJunkScoreOrigin(const char *aJunkScoreOrigin, PRBool *pResult)
{
  NS_ENSURE_ARG_POINTER(pResult);
  PRBool matches = PR_FALSE;
  nsresult rv = NS_OK;

  switch (m_operator)
  {
  case nsMsgSearchOp::Is:
    matches = aJunkScoreOrigin && !strcmp(aJunkScoreOrigin, m_value.string);
    break;
  case nsMsgSearchOp::Isnt:
    matches = !aJunkScoreOrigin || strcmp(aJunkScoreOrigin, m_value.string);
    break;
  default:
    rv = NS_ERROR_FAILURE;
    NS_ASSERTION(PR_FALSE, "invalid compare op for junk score origin");
  }

  *pResult = matches;
  return rv;
}

nsresult nsMsgSearchTerm::MatchJunkPercent(PRUint32 aJunkPercent, PRBool *pResult)
{
  NS_ENSURE_ARG_POINTER(pResult);
  PRBool result = PR_FALSE;
  switch (m_operator)
  {
  case nsMsgSearchOp::IsGreaterThan:
    if (aJunkPercent > m_value.u.junkPercent)
      result = PR_TRUE;
    break;
  case nsMsgSearchOp::IsLessThan:
    if (aJunkPercent < m_value.u.junkPercent)
      result = PR_TRUE;
    break;
  case nsMsgSearchOp::Is:
    if (aJunkPercent == m_value.u.junkPercent)
      result = PR_TRUE;
    break;
  default:
    break;
  }
  *pResult = result;
  return NS_OK;
}
  

nsresult nsMsgSearchTerm::MatchLabel(nsMsgLabelValue aLabelValue, PRBool *pResult)
{
  NS_ENSURE_ARG_POINTER(pResult);
  PRBool result = PR_FALSE;
  switch (m_operator)
  {
  case nsMsgSearchOp::Is:
    if (m_value.u.label == aLabelValue)
      result = PR_TRUE;
    break;
  default:
    if (m_value.u.label != aLabelValue)
      result = PR_TRUE;
    break;
  }

  *pResult = result;
  return NS_OK;
}

nsresult nsMsgSearchTerm::MatchStatus(PRUint32 statusToMatch, PRBool *pResult)
{
  NS_ENSURE_ARG_POINTER(pResult);

  nsresult rv = NS_OK;
  PRBool matches = (statusToMatch & m_value.u.msgStatus);

  switch (m_operator)
  {
  case nsMsgSearchOp::Is:
    break;
  case nsMsgSearchOp::Isnt:
    matches = !matches;
    break;
  default:
    rv = NS_ERROR_FAILURE;
    NS_ERROR("invalid compare op for msg status");
  }

  *pResult = matches;
  return rv;
}

/*
 * MatchKeyword Logic table (*pResult: + is true, - is false)
 *
 *         # Valid Tokens IsEmpty IsntEmpty Contains DoesntContain Is     Isnt
 *                0           +         -      -         +         -       +
 * Term found?                               N   Y     N   Y     N   Y   N   Y
 *                1           -         +    -   +     +   -     -   +   +   -
 *               >1           -         +    -   +     +   -     -   -   +   +
 */
// look up nsMsgSearchTerm::m_value in space-delimited keywordList
nsresult nsMsgSearchTerm::MatchKeyword(const nsACString& keywordList, PRBool *pResult)
{
  NS_ENSURE_ARG_POINTER(pResult);
  PRBool matches = PR_FALSE;

  // special-case empty for performance reasons
  if (keywordList.IsEmpty())
  {
    *pResult =  m_operator != nsMsgSearchOp::Contains &&
                m_operator != nsMsgSearchOp::Is &&
                m_operator != nsMsgSearchOp::IsntEmpty;
    return NS_OK;
  }

  // check if we can skip expensive valid keywordList test
  if (m_operator == nsMsgSearchOp::DoesntContain ||
      m_operator == nsMsgSearchOp::Contains)
  {
    nsCString keywordString(keywordList);
    const PRUint32 kKeywordLen = PL_strlen(m_value.string);
    const char* matchStart = PL_strstr(keywordString.get(), m_value.string);
    while (matchStart)
    {
      // For a real match, matchStart must be the start of the keywordList or
      // preceded by a space and matchEnd must point to a \0 or space.
      const char* matchEnd = matchStart + kKeywordLen;
      if ((matchStart == keywordString.get() || matchStart[-1] == ' ') &&
          (!*matchEnd || *matchEnd == ' '))
      {
        // found the keyword
        *pResult = m_operator == nsMsgSearchOp::Contains;
        return NS_OK;
      }
      // no match yet, so search on
      matchStart = PL_strstr(matchEnd, m_value.string);
    }
    // keyword not found
    *pResult = m_operator == nsMsgSearchOp::DoesntContain;
    return NS_OK;
  }

  // Only accept valid keys in tokens.
  nsresult rv = NS_OK;
  nsTArray<nsCString> keywordArray;
  ParseString(keywordList, ' ', keywordArray);
  nsCOMPtr<nsIMsgTagService> tagService(do_GetService(NS_MSGTAGSERVICE_CONTRACTID, &rv));
  NS_ENSURE_SUCCESS(rv, rv);

  // Loop through tokens in keywords
  PRUint32 count = keywordArray.Length();
  for (PRUint32 i = 0; i < count; i++)
  {
    // is this token a valid tag? Otherwise ignore it
    PRBool isValid;
    rv = tagService->IsValidKey(keywordArray[i], &isValid);
    NS_ENSURE_SUCCESS(rv, rv);

    if (isValid)
    {
      // IsEmpty fails on any valid token
      if (m_operator == nsMsgSearchOp::IsEmpty)
      {
        *pResult = PR_FALSE;
        return rv;
      }

      // IsntEmpty succeeds on any valid token
      if (m_operator == nsMsgSearchOp::IsntEmpty)
      {
        *pResult = PR_TRUE;
        return rv;
      }

      // Does this valid tag key match our search term?
      matches = keywordArray[i].Equals(m_value.string);

      // Is or Isn't partly determined on a single unmatched token
      if (!matches)
      {
        if (m_operator == nsMsgSearchOp::Is)
        {
          *pResult = PR_FALSE;
          return rv;
        }
        if (m_operator == nsMsgSearchOp::Isnt)
        {
          *pResult = PR_TRUE;
          return rv;
        }
      }
    }
  }

  if (m_operator == nsMsgSearchOp::Is)
  {
    *pResult = matches;
    return NS_OK;
  }

  if (m_operator == nsMsgSearchOp::Isnt)
  {
    *pResult = !matches;
    return NS_OK;
  }

  if (m_operator == nsMsgSearchOp::IsEmpty)
  {
    *pResult = PR_TRUE;
    return NS_OK;
  }

  if (m_operator == nsMsgSearchOp::IsntEmpty)
  {
    *pResult = PR_FALSE;
    return NS_OK;
  }


  // no valid match operator found
  NS_ERROR("invalid compare op for msg status");
  return NS_ERROR_FAILURE;
}

nsresult
nsMsgSearchTerm::MatchPriority (nsMsgPriorityValue priorityToMatch,
                                PRBool *pResult)
{
  NS_ENSURE_ARG_POINTER(pResult);

  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 = (priorityToMatch == nsMsgPriority::none) ? (int) nsMsgPriority::normal : (int) priorityToMatch;
  int p2 = (int) m_value.u.priority;

  switch (m_operator)
  {
  case nsMsgSearchOp::IsHigherThan:
    if (p1 > p2)
      result = 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;

  if (!m_headerAddressParser)
  {
    m_headerAddressParser = do_GetService(NS_MAILNEWS_MIME_HEADER_PARSER_CONTRACTID, &res);
  }
  return res;
}

NS_IMPL_GETSET(nsMsgSearchTerm, Attrib, nsMsgSearchAttribValue, m_attribute)
NS_IMPL_GETSET(nsMsgSearchTerm, Op, nsMsgSearchOpValue, m_operator)
NS_IMPL_GETSET(nsMsgSearchTerm, MatchAll, PRBool, m_matchAll)

NS_IMETHODIMP
nsMsgSearchTerm::GetValue(nsIMsgSearchValue **aResult)
{
    NS_ENSURE_ARG_POINTER(aResult);
    *aResult = new nsMsgSearchValueImpl(&m_value);
    NS_IF_ADDREF(*aResult);
    return NS_OK;
}

NS_IMETHODIMP
nsMsgSearchTerm::SetValue(nsIMsgSearchValue* aValue)
{
  nsMsgResultElement::AssignValues (aValue, &m_value);
  return NS_OK;
}

NS_IMETHODIMP
nsMsgSearchTerm::GetBooleanAnd(PRBool *aResult)
{
    NS_ENSURE_ARG_POINTER(aResult);
    *aResult = (m_booleanOp == nsMsgSearchBooleanOp::BooleanAND);
    return NS_OK;
}

NS_IMETHODIMP
nsMsgSearchTerm::SetBooleanAnd(PRBool aValue)
{
    if (aValue)
        m_booleanOp = nsMsgSearchBooleanOperator(nsMsgSearchBooleanOp::BooleanAND);
    else
        m_booleanOp = nsMsgSearchBooleanOperator(nsMsgSearchBooleanOp::BooleanOR);
    return NS_OK;
}

NS_IMETHODIMP
nsMsgSearchTerm::GetArbitraryHeader(nsACString &aResult)
{
    aResult = m_arbitraryHeader;
    return NS_OK;
}

NS_IMETHODIMP
nsMsgSearchTerm::SetArbitraryHeader(const nsACString &aValue)
{
    m_arbitraryHeader = aValue;
    ToLowerCaseExceptSpecials(m_arbitraryHeader);
    return NS_OK;
}

NS_IMPL_GETSET(nsMsgSearchTerm, BeginsGrouping, PRBool, mEndsGrouping)
NS_IMPL_GETSET(nsMsgSearchTerm, EndsGrouping, PRBool, mEndsGrouping)

//
// Certain possible standard values of a message database row also sometimes
// appear as header values. To prevent a naming collision, we use all
// lower case for the standard headers, and first capital when those
// same strings are requested as arbitrary headers. This routine is used
// when setting arbitrary headers.
//
void nsMsgSearchTerm::ToLowerCaseExceptSpecials(nsACString &aValue)
{
#ifdef MOZILLA_INTERNAL_API
  if (NS_LITERAL_CSTRING("Sender").Equals(aValue, nsCaseInsensitiveCStringComparator()))
    aValue.Assign(NS_LITERAL_CSTRING("Sender"));
  else if (NS_LITERAL_CSTRING("Date").Equals(aValue, nsCaseInsensitiveCStringComparator()))
    aValue.Assign(NS_LITERAL_CSTRING("Date"));
  else if (NS_LITERAL_CSTRING("Status").Equals(aValue, nsCaseInsensitiveCStringComparator()))
    aValue.Assign(NS_LITERAL_CSTRING("Status"));
#else
  if (NS_LITERAL_CSTRING("Sender").Equals(aValue, CaseInsensitiveCompare))
    aValue.Assign(NS_LITERAL_CSTRING("Sender"));
  else if (NS_LITERAL_CSTRING("Date").Equals(aValue, CaseInsensitiveCompare))
    aValue.Assign(NS_LITERAL_CSTRING("Date"));
  else if (NS_LITERAL_CSTRING("Status").Equals(aValue, CaseInsensitiveCompare))
    aValue.Assign(NS_LITERAL_CSTRING("Status"));
#endif
  else
    ToLowerCase(aValue);
}


//-----------------------------------------------------------------------------
// nsMsgSearchScopeTerm implementation
//-----------------------------------------------------------------------------
nsMsgSearchScopeTerm::nsMsgSearchScopeTerm (nsIMsgSearchSession *session,
                                            nsMsgSearchScopeValue attribute,
                                            nsIMsgFolder *folder)
{
  m_attribute = attribute;
  m_folder = folder;
  m_searchServer = PR_TRUE;
  m_searchSession = do_GetWeakReference(session);
}

nsMsgSearchScopeTerm::nsMsgSearchScopeTerm ()
{
  m_searchServer = PR_TRUE;
}

nsMsgSearchScopeTerm::~nsMsgSearchScopeTerm ()
{
  if (m_inputStream)
    m_inputStream->Close();
  m_inputStream = nsnull;
}

NS_IMPL_ISUPPORTS1(nsMsgSearchScopeTerm, nsIMsgSearchScopeTerm)

NS_IMETHODIMP
nsMsgSearchScopeTerm::GetFolder(nsIMsgFolder **aResult)
{
    NS_IF_ADDREF(*aResult = m_folder);
    return NS_OK;
}

NS_IMETHODIMP
nsMsgSearchScopeTerm::GetSearchSession(nsIMsgSearchSession** aResult)
{
    NS_ENSURE_ARG_POINTER(aResult);
    nsCOMPtr<nsIMsgSearchSession> searchSession = do_QueryReferent (m_searchSession);
    NS_IF_ADDREF(*aResult = searchSession);
    return NS_OK;
}

NS_IMETHODIMP nsMsgSearchScopeTerm::GetMailFile(nsILocalFile **aLocalFile)
{
  NS_ENSURE_ARG_POINTER(aLocalFile);
  if (!m_localFile)
  {
    if (!m_folder)
     return NS_ERROR_NULL_POINTER;

    m_folder->GetFilePath(getter_AddRefs(m_localFile));
  }
  if (m_localFile)
  {
    NS_ADDREF(*aLocalFile = m_localFile);
    return NS_OK;
  }
  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP nsMsgSearchScopeTerm::GetInputStream(nsIInputStream **aInputStream)
{
  NS_ENSURE_ARG_POINTER(aInputStream);
  nsresult rv = NS_OK;
  if (!m_inputStream)
  {
     nsCOMPtr <nsILocalFile> localFile;
     rv = GetMailFile(getter_AddRefs(localFile));
     NS_ENSURE_SUCCESS(rv, rv);
     nsCOMPtr<nsIFileInputStream> fileStream = do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);

     rv = fileStream->Init(localFile,  PR_RDONLY, 0664, PR_FALSE);  //just have to read the messages
     m_inputStream = do_QueryInterface(fileStream);

  }
  NS_IF_ADDREF(*aInputStream = m_inputStream);
  return rv;
}

NS_IMETHODIMP nsMsgSearchScopeTerm::SetInputStream(nsIInputStream *aInputStream)
{
  if (!aInputStream && m_inputStream)
    m_inputStream->Close();
  m_inputStream = aInputStream;
  return NS_OK;
}

nsresult nsMsgSearchScopeTerm::TimeSlice (PRBool *aDone)
{
  return m_adapter->Search(aDone);
}

nsresult nsMsgSearchScopeTerm::InitializeAdapter (nsISupportsArray *termList)
{
  if (m_adapter)
    return NS_OK;

  nsresult err = NS_OK;

  switch (m_attribute)
  {
    case nsMsgSearchScope::onlineMail:
        m_adapter = new nsMsgSearchOnlineMail (this, termList);
      break;
    case nsMsgSearchScope::offlineMail:
        m_adapter = new nsMsgSearchOfflineMail (this, termList);
      break;
    case nsMsgSearchScope::newsEx:
      NS_ASSERTION(PR_FALSE, "not supporting newsEx yet");
      break;
    case nsMsgSearchScope::news:
          m_adapter = new nsMsgSearchNews (this, termList);
        break;
    case nsMsgSearchScope::allSearchableGroups:
      NS_ASSERTION(PR_FALSE, "not supporting allSearchableGroups yet");
      break;
    case nsMsgSearchScope::LDAP:
      NS_ASSERTION(PR_FALSE, "not supporting LDAP yet");
      break;
    case nsMsgSearchScope::localNews:
      m_adapter = new nsMsgSearchOfflineNews (this, termList);
      break;
    default:
      NS_ASSERTION(PR_FALSE, "invalid scope");
      err = NS_ERROR_FAILURE;
  }

  if (m_adapter)
    err = m_adapter->ValidateTerms ();

  return err;
}


char *nsMsgSearchScopeTerm::GetStatusBarName ()
{
  return nsnull;
}


//-----------------------------------------------------------------------------
// nsMsgResultElement implementation
//-----------------------------------------------------------------------------


nsMsgResultElement::nsMsgResultElement(nsIMsgSearchAdapter *adapter)
{
  NS_NewISupportsArray(getter_AddRefs(m_valueList));
  m_adapter = adapter;
}


nsMsgResultElement::~nsMsgResultElement ()
{
}


nsresult nsMsgResultElement::AddValue (nsIMsgSearchValue *value)
{
  m_valueList->AppendElement (value);
  return NS_OK;
}

nsresult nsMsgResultElement::AddValue (nsMsgSearchValue *value)
{
  nsMsgSearchValueImpl* valueImpl = new nsMsgSearchValueImpl(value);
  delete value;               // we keep the nsIMsgSearchValue, not
                              // the nsMsgSearchValue
  return AddValue(valueImpl);
}


nsresult nsMsgResultElement::AssignValues (nsIMsgSearchValue *src, nsMsgSearchValue *dst)
{
  NS_ENSURE_ARG_POINTER(src);
  NS_ENSURE_ARG_POINTER(dst);

  // Yes, this could be an operator overload, but nsMsgSearchValue is totally public, so I'd
  // have to define a derived class with nothing by operator=, and that seems like a bit much
  nsresult err = NS_OK;
  src->GetAttrib(&dst->attribute);
  switch (dst->attribute)
  {
  case nsMsgSearchAttrib::Priority:
    err = src->GetPriority(&dst->u.priority);
    break;
  case nsMsgSearchAttrib::Date:
    err = src->GetDate(&dst->u.date);
    break;
  case nsMsgSearchAttrib::HasAttachmentStatus:
  case nsMsgSearchAttrib::MsgStatus:
    err = src->GetStatus(&dst->u.msgStatus);
    break;
  case nsMsgSearchAttrib::MessageKey:
    err = src->GetMsgKey(&dst->u.key);
    break;
  case nsMsgSearchAttrib::AgeInDays:
    err = src->GetAge(&dst->u.age);
    break;
  case nsMsgSearchAttrib::Label:
    err = src->GetLabel(&dst->u.label);
    break;
  case nsMsgSearchAttrib::JunkStatus:
    err = src->GetJunkStatus(&dst->u.junkStatus);
    break;
  case nsMsgSearchAttrib::JunkPercent:
    err = src->GetJunkPercent(&dst->u.junkPercent);
    break;
  case nsMsgSearchAttrib::Size:
    err = src->GetSize(&dst->u.size);
    break;
  default:
    if (dst->attribute < nsMsgSearchAttrib::kNumMsgSearchAttributes)
    {
      NS_ASSERTION(IS_STRING_ATTRIBUTE(dst->attribute), "assigning non-string result");
      nsString unicodeString;
      err = src->GetStr(unicodeString);
      dst->string = ToNewUTF8String(unicodeString);
    }
    else
      err = NS_ERROR_INVALID_ARG;
  }
  return err;
}


nsresult nsMsgResultElement::GetValue (nsMsgSearchAttribValue attrib,
                                       nsMsgSearchValue **outValue) const
{
  nsresult err = NS_OK;
  nsCOMPtr<nsIMsgSearchValue> value;
  *outValue = NULL;

  PRUint32 count;
  m_valueList->Count(&count);
  for (PRUint32 i = 0; i < count && err != NS_OK; i++)
  {
    m_valueList->QueryElementAt(i, NS_GET_IID(nsIMsgSearchValue),
      (void **)getter_AddRefs(value));

    nsMsgSearchAttribValue valueAttribute;
    value->GetAttrib(&valueAttribute);
    if (attrib == valueAttribute)
    {
      *outValue = new nsMsgSearchValue;
      if (*outValue)
      {
        err = AssignValues (value, *outValue);
        err = NS_OK;
      }
      else
        err = NS_ERROR_OUT_OF_MEMORY;
    }
  }
  return err;
}

nsresult nsMsgResultElement::GetPrettyName (nsMsgSearchValue **value)
{
  return GetValue (nsMsgSearchAttrib::Location, value);
}

nsresult nsMsgResultElement::Open (void *window)
{
  return NS_ERROR_NULL_POINTER;
}