/* -*- 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"typedefstruct{nsMsgSearchAttribValueattrib;constchar*attribName;}nsMsgSearchAttribEntry;nsMsgSearchAttribEntrySearchAttribEntryTable[]={{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 headernsresultNS_MsgGetAttributeFromString(constchar*string,PRInt16*attrib){NS_ENSURE_ARG_POINTER(string);NS_ENSURE_ARG_POINTER(attrib);PRBoolfound=PR_FALSE;// custom headers have a leading quoteif(*string!='"'){for(intidxAttrib=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 quotestring++;if(!found){nsresultrv;PRBoolgoodHdr;IsRFC822HeaderFieldName(string,&goodHdr);if(!goodHdr)returnNS_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);nsCStringheaders;prefBranch->GetCharPref(MAILNEWS_CUSTOM_HEADERS,getter_Copies(headers));if(!headers.IsEmpty()){nsCAutoStringhdrStr(headers);hdrStr.StripWhitespace();//remove whitespace before parsingchar*newStr=hdrStr.BeginWriting();char*token=NS_strtok(":",&newStr);PRUint32i=0;while(token){if(PL_strcasecmp(token,string)==0){*attrib+=i;//we found custom header in the preffound=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.returnNS_OK;}nsresultNS_MsgGetStringForAttribute(PRInt16attrib,constchar**string){NS_ENSURE_ARG_POINTER(string);PRBoolfound=PR_FALSE;for(intidxAttrib=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 notreturnNS_OK;}typedefstruct{nsMsgSearchOpValueop;constchar*opName;}nsMsgSearchOperatorEntry;nsMsgSearchOperatorEntrySearchOperatorEntryTable[]={{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"}};nsresultNS_MsgGetOperatorFromString(constchar*string,PRInt16*op){NS_ENSURE_ARG_POINTER(string);NS_ENSURE_ARG_POINTER(op);PRBoolfound=PR_FALSE;for(unsignedintidxOp=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;}nsresultNS_MsgGetStringForOperator(PRInt16op,constchar**string){NS_ENSURE_ARG_POINTER(string);PRBoolfound=PR_FALSE;for(unsignedintidxOp=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;}voidNS_MsgGetUntranslatedStatusName(uint32s,nsCString*outName){constchar*tmpOutName=NULL;#define MSG_STATUS_MASK (nsMsgMessageFlags::Read | nsMsgMessageFlags::Replied |\ nsMsgMessageFlags::Forwarded | nsMsgMessageFlags::New | nsMsgMessageFlags::Marked)PRUint32maskOut=(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;elseif(maskOut&nsMsgMessageFlags::Forwarded)maskOut=nsMsgMessageFlags::Forwarded;elseif(maskOut&nsMsgMessageFlags::Replied)maskOut=nsMsgMessageFlags::Replied;switch(maskOut){casensMsgMessageFlags::Read:tmpOutName="read";break;casensMsgMessageFlags::Replied:tmpOutName="replied";break;casensMsgMessageFlags::Forwarded:tmpOutName="forwarded";break;casensMsgMessageFlags::Forwarded|nsMsgMessageFlags::Replied:tmpOutName="replied and forwarded";break;casensMsgMessageFlags::New:tmpOutName="new";break;casensMsgMessageFlags::Marked:tmpOutName="flagged";break;default:// This is fine, status may be "unread" for examplebreak;}if(tmpOutName)*outName=tmpOutName;}PRInt32NS_MsgGetStatusValueFromName(char*name){if(!strcmp("read",name))returnnsMsgMessageFlags::Read;if(!strcmp("replied",name))returnnsMsgMessageFlags::Replied;if(!strcmp("forwarded",name))returnnsMsgMessageFlags::Forwarded;if(!strcmp("replied and forwarded",name))returnnsMsgMessageFlags::Forwarded|nsMsgMessageFlags::Replied;if(!strcmp("new",name))returnnsMsgMessageFlags::New;if(!strcmp("flagged",name))returnnsMsgMessageFlags::Marked;return0;}// Needed for DeStream method.nsMsgSearchTerm::nsMsgSearchTerm(){// initialize this to zerom_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(nsMsgSearchAttribValueattrib,nsMsgSearchOpValueop,nsIMsgSearchValue*val,nsMsgSearchBooleanOperatorboolOp,constchar*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(constchar*str){intnumQuotes=0;for(constchar*strPtr=str;*strPtr;strPtr++)if(*strPtr=='"')numQuotes++;intescapedStrLen=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';}returnescapedStr;}nsresultnsMsgSearchTerm::OutputValue(nsCString&outputStr){if(IS_STRING_ATTRIBUTE(m_attribute)&&m_value.string){PRBoolquoteVal=PR_FALSE;// need to quote strings with ')' and strings starting with '"' or ' '// filter code will escape quotesif(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){casensMsgSearchAttrib::Date:{PRExplodedTimeexploded;PR_ExplodeTime(m_value.u.date,PR_LocalTimeParameters,&exploded);// wow, so tm_mon is 0 based, tm_mday is 1 based.chardateBuf[100];PR_FormatTimeUSEnglish(dateBuf,sizeof(dateBuf),"%d-%b-%Y",&exploded);outputStr+=dateBuf;break;}casensMsgSearchAttrib::AgeInDays:{outputStr.AppendInt(m_value.u.age);break;}casensMsgSearchAttrib::Label:{outputStr.AppendInt(m_value.u.label);break;}casensMsgSearchAttrib::JunkStatus:{outputStr.AppendInt(m_value.u.junkStatus);// only if we write to disk, right?break;}casensMsgSearchAttrib::JunkPercent:{outputStr.AppendInt(m_value.u.junkPercent);break;}casensMsgSearchAttrib::MsgStatus:{nsCAutoStringstatus;NS_MsgGetUntranslatedStatusName(m_value.u.msgStatus,&status);outputStr+=status;break;}casensMsgSearchAttrib::Priority:{nsCAutoStringpriority;NS_MsgGetUntranslatedPriorityName(m_value.u.priority,priority);outputStr+=priority;break;}casensMsgSearchAttrib::HasAttachmentStatus:{outputStr.Append("true");// don't need anything here, reallybreak;}casensMsgSearchAttrib::Size:{outputStr.AppendInt(m_value.u.size);break;}default:NS_ASSERTION(PR_FALSE,"trying to output invalid attribute");break;}}returnNS_OK;}NS_IMETHODIMPnsMsgSearchTerm::GetTermAsString(nsACString&outStream){constchar*operatorStr;nsCAutoStringoutputStr;nsresultret;if(m_matchAll){outStream="ALL";returnNS_OK;}if(m_attribute>nsMsgSearchAttrib::OtherHeader&&m_attribute<nsMsgSearchAttrib::kNumMsgSearchAttributes)// if arbitrary header, use it instead!{outputStr="\"";outputStr+=m_arbitraryHeader;outputStr+="\"";}else{constchar*attrib;ret=NS_MsgGetStringForAttribute(m_attribute,&attrib);if(ret!=NS_OK)returnret;outputStr=attrib;}outputStr+=',';ret=NS_MsgGetStringForOperator(m_operator,&operatorStr);if(ret!=NS_OK)returnret;outputStr+=operatorStr;outputStr+=',';OutputValue(outputStr);outStream=outputStr;returnNS_OK;}// fill in m_value from the input stream.nsresultnsMsgSearchTerm::ParseValue(char*inStream){if(IS_STRING_ATTRIBUTE(m_attribute)){PRBoolquoteVal=PR_FALSE;while(isspace(*inStream))inStream++;// need to remove pair of '"', if presentif(*inStream=='"'){quoteVal=PR_TRUE;inStream++;}intvalueLen=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){casensMsgSearchAttrib::Date:PR_ParseTimeString(inStream,PR_FALSE,&m_value.u.date);break;casensMsgSearchAttrib::MsgStatus:m_value.u.msgStatus=NS_MsgGetStatusValueFromName(inStream);break;casensMsgSearchAttrib::Priority:NS_MsgGetPriorityFromString(inStream,m_value.u.priority);break;casensMsgSearchAttrib::AgeInDays:m_value.u.age=atoi(inStream);break;casensMsgSearchAttrib::Label:m_value.u.label=atoi(inStream);break;casensMsgSearchAttrib::JunkStatus:m_value.u.junkStatus=atoi(inStream);// only if we read from disk, right?break;casensMsgSearchAttrib::JunkPercent:m_value.u.junkPercent=atoi(inStream);break;casensMsgSearchAttrib::HasAttachmentStatus:m_value.u.msgStatus=nsMsgMessageFlags::Attachment;break;// this should always be true.casensMsgSearchAttrib::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;returnNS_OK;}// find the operator code for this operator string.nsresultnsMsgSearchTerm::ParseOperator(char*inStream,nsMsgSearchOpValue*value){NS_ENSURE_ARG_POINTER(value);PRInt16operatorVal;while(isspace(*inStream))inStream++;char*commaSep=PL_strchr(inStream,',');if(commaSep)*commaSep='\0';nsresulterr=NS_MsgGetOperatorFromString(inStream,&operatorVal);*value=(nsMsgSearchOpValue)operatorVal;returnerr;}// find the attribute code for this comma-delimited attribute.nsresultnsMsgSearchTerm::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.PRBoolquoteVal=PR_FALSE;if(*inStream=='"')quoteVal=PR_TRUE;// arbitrary headers are quoted. Skip first character, which will be the// first quote for arbitrary headerschar*separator=strchr(inStream+1,quoteVal?'"':',');if(separator)*separator='\0';PRInt16attributeVal;nsresultrv=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 quoteToLowerCaseExceptSpecials(m_arbitraryHeader);}returnrv;}// 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"nsresultnsMsgSearchTerm::DeStreamNew(char*inStream,PRInt16/*length*/){if(!strcmp(inStream,"ALL")){m_matchAll=PR_TRUE;returnNS_OK;}char*commaSep=PL_strchr(inStream,',');nsresultrv=ParseAttribute(inStream,&m_attribute);// will allocate space for arbitrary header if necessaryNS_ENSURE_SUCCESS(rv,rv);if(!commaSep)returnNS_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 equivalentsif(secondCommaSep)ParseValue(secondCommaSep+1);if(m_attribute==nsMsgSearchAttrib::Label){nsCAutoStringkeyword("$label");m_value.attribute=m_attribute=nsMsgSearchAttrib::Keywords;keyword.Append('0'+m_value.u.label);m_value.string=PL_strdup(keyword.get());}returnNS_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.nsresultnsMsgSearchTerm::MatchArbitraryHeader(nsIMsgSearchScopeTerm*scope,PRUint32offset,PRUint32length/* in lines*/,constchar*charset,PRBoolcharsetOverride,nsIMsgDBHdr*msg,nsIMsgDatabase*db,constchar*headers,PRUint32headersSize,PRBoolForFiltering,PRBool*pResult){NS_ENSURE_ARG_POINTER(pResult);*pResult=PR_FALSE;nsresulterr=NS_OK;PRBoolresult;GetMatchAllBeforeDeciding(&result);nsCStringdbHdrValue;msg->GetStringProperty(m_arbitraryHeader.get(),getter_Copies(dbHdrValue));if(!dbHdrValue.IsEmpty()){PRBoolresult2;err=MatchRfc2047String(dbHdrValue.get(),charset,charsetOverride,&result2);// match value with the other info...if(result!=result2)// if we found a matchresult=result2;*pResult=result;returnerr;}nsMsgBodyHandler*bodyHandler=newnsMsgBodyHandler(scope,offset,length,msg,db,headers,headersSize,ForFiltering);if(!bodyHandler)returnNS_ERROR_OUT_OF_MEMORY;bodyHandler->SetStripHeaders(PR_FALSE);nsCAutoStringbuf;nsCAutoStringcurMsgHeader;PRBoolsearchingHeaders=PR_TRUE;while(searchingHeaders&&(bodyHandler->GetNextLine(buf)>=0)){char*buf_end=(char*)(buf.get()+buf.Length());intheaderLength=m_arbitraryHeader.Length();PRBoolisContinuationHeader=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){PRUint32colonPos=buf.FindChar(':');curMsgHeader=StringHead(buf,colonPos);}#ifdef MOZILLA_INTERNAL_APIif(curMsgHeader.Equals(m_arbitraryHeader,nsCaseInsensitiveCStringComparator()))#elseif(curMsgHeader.Equals(m_arbitraryHeader,CaseInsensitiveCompare))#endif{// value occurs after the header name or whitespace continuation char.constchar*headerValue=buf.get()+(isContinuationHeader?1:headerLength);if(headerValue<buf_end&&headerValue[0]==':')// + 1 to account for the colon which is MANDATORYheaderValue++;// strip leading white spacewhile(headerValue<buf_end&&isspace(*headerValue))headerValue++;// advance to next character// strip trailing white spacechar*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 spaceend--;// move back and examine the previous character....}if(headerValue<buf_end&&*headerValue)// make sure buf has info besides just the header{PRBoolresult2;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 headersresult=result2;}}elseNS_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;}deletebodyHandler;*pResult=result;returnerr;}nsresultnsMsgSearchTerm::MatchBody(nsIMsgSearchScopeTerm*scope,PRUint32offset,PRUint32length/*in lines*/,constchar*folderCharset,nsIMsgDBHdr*msg,nsIMsgDatabase*db,PRBool*pResult){NS_ENSURE_ARG_POINTER(pResult);nsresulterr=NS_OK;PRBoolresult=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=newnsMsgBodyHandler(scope,offset,length,msg,db);if(!bodyHan)returnNS_ERROR_OUT_OF_MEMORY;nsCAutoStringbuf;PRBoolendOfFile=PR_FALSE;// if retValue == 0, we've hit the end of the fileuint32lines=0;// Change the sense of the loop so we don't bail out prematurely// on negative terms. i.e. opDoesntContain must look at all linesPRBoolboolContinueLoop;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 '='.PRBoolisQuotedPrintable=!nsMsgI18Nstateful_charset(folderCharset)&&(PL_strchr(m_value.string,'=')==nsnull);nsCStringcompare;while(!endOfFile&&result==boolContinueLoop){if(bodyHan->GetNextLine(buf)>=0){PRBoolsoftLineBreak=PR_FALSE;// Do in-place decoding of quoted printableif(isQuotedPrintable){softLineBreak=StringEndsWith(buf,NS_LITERAL_CSTRING("="));MsgStripQuotedPrintable((unsignedchar*)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()){charstartChar=(char)compare.CharAt(0);if(startChar!='\r'&&startChar!='\n'){err=MatchString(compare.get(),folderCharset,&result);lines++;}compare.Truncate();}}elseendOfFile=PR_TRUE;}#ifdef DO_I18Nif(conv)INTL_DestroyCharCodeConverter(conv);#endifdeletebodyHan;*pResult=result;returnerr;}nsresultnsMsgSearchTerm::InitializeAddressBook(){// the search attribute value has the URI for the address book we need to load.// we need both the database and the directory.nsresultrv=NS_OK;if(mDirectory){nsCStringuri;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 onemDirectory=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);}returnNS_OK;}nsresultnsMsgSearchTerm::MatchInAddressBook(constchar*aAddress,PRBool*pResult){nsresultrv=InitializeAddressBook();*pResult=PR_FALSE;// Some junkmails have empty From: fields.if(aAddress==NULL||strlen(aAddress)==0)returnrv;if(mDirectory){nsIAbCard*cardForAddress=nsnull;rv=mDirectory->CardForEmailAddress(nsDependentCString(aAddress),&cardForAddress);if(NS_FAILED(rv)&&rv!=NS_ERROR_NOT_IMPLEMENTED)returnrv;if((m_operator==nsMsgSearchOp::IsInAB&&cardForAddress)||(m_operator==nsMsgSearchOp::IsntInAB&&!cardForAddress))*pResult=PR_TRUE;NS_IF_RELEASE(cardForAddress);}returnrv;}// *pResult is PR_FALSE when strings don't match, PR_TRUE if they do.nsresultnsMsgSearchTerm::MatchRfc2047String(constchar*rfc2047string,constchar*charset,PRBoolcharsetOverride,PRBool*pResult){NS_ENSURE_ARG_POINTER(pResult);NS_ENSURE_ARG_POINTER(rfc2047string);nsCOMPtr<nsIMimeConverter>mimeConverter=do_GetService(NS_MIME_CONVERTER_CONTRACTID);char*stringToMatch=0;nsresultres=mimeConverter->DecodeMimeHeaderToCharPtr(rfc2047string,charset,charsetOverride,PR_FALSE,&stringToMatch);if(m_operator==nsMsgSearchOp::IsInAB||m_operator==nsMsgSearchOp::IsntInAB){res=MatchInAddressBook(stringToMatch?stringToMatch:rfc2047string,pResult);}elseres=MatchString(stringToMatch?stringToMatch:rfc2047string,nsnull,pResult);PR_Free(stringToMatch);returnres;}// *pResult is PR_FALSE when strings don't match, PR_TRUE if they do.nsresultnsMsgSearchTerm::MatchString(constchar*stringToMatch,constchar*charset,PRBool*pResult){NS_ENSURE_ARG_POINTER(pResult);PRBoolresult=PR_FALSE;nsresulterr=NS_OK;nsAutoStringutf16StrToMatch;nsAutoStringneedle;// Save some performance for opIsEmpty / opIsntEmptyif(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){casensMsgSearchOp::Contains:#ifdef MOZILLA_INTERNAL_APIif(CaseInsensitiveFindInReadable(needle,utf16StrToMatch))#elseif(utf16StrToMatch.Find(needle,CaseInsensitiveCompare)!=-1);#endifresult=PR_TRUE;break;casensMsgSearchOp::DoesntContain:#ifdef MOZILLA_INTERNAL_APIif(!CaseInsensitiveFindInReadable(needle,utf16StrToMatch))#elseif(utf16StrToMatch.Find(needle,CaseInsensitiveCompare)==-1);#endifresult=PR_TRUE;break;casensMsgSearchOp::Is:#ifdef MOZILLA_INTERNAL_APIif(needle.Equals(utf16StrToMatch,nsCaseInsensitiveStringComparator()))#elseif(needle.Equals(utf16StrToMatch,CaseInsensitiveCompare))#endifresult=PR_TRUE;break;casensMsgSearchOp::Isnt:#ifdef MOZILLA_INTERNAL_APIif(!needle.Equals(utf16StrToMatch,nsCaseInsensitiveStringComparator()))#elseif(!needle.Equals(utf16StrToMatch,CaseInsensitiveCompare))#endifresult=PR_TRUE;break;casensMsgSearchOp::IsEmpty:// For IsEmpty, we didn't copy stringToMatch to utf16StrToMatch.if(!PL_strlen(stringToMatch))result=PR_TRUE;break;casensMsgSearchOp::IsntEmpty:// For IsntEmpty, we didn't copy stringToMatch to utf16StrToMatch.if(PL_strlen(stringToMatch))result=PR_TRUE;break;casensMsgSearchOp::BeginsWith:#ifdef MOZILLA_INTERNAL_APIif(StringBeginsWith(utf16StrToMatch,needle,nsCaseInsensitiveStringComparator()))#elseif(StringBeginsWith(utf16StrToMatch,needle,CaseInsensitiveCompare))#endifresult=PR_TRUE;break;casensMsgSearchOp::EndsWith:#ifdef MOZILLA_INTERNAL_APIif(StringEndsWith(utf16StrToMatch,needle,nsCaseInsensitiveStringComparator()))#elseif(StringEndsWith(utf16StrToMatch,needle,CaseInsensitiveCompare))#endifresult=PR_TRUE;break;default:NS_ASSERTION(PR_FALSE,"invalid operator matching search results");}*pResult=result;returnerr;}NS_IMETHODIMPnsMsgSearchTerm::GetMatchAllBeforeDeciding(PRBool*aResult){*aResult=(m_operator==nsMsgSearchOp::DoesntContain||m_operator==nsMsgSearchOp::Isnt);returnNS_OK;}nsresultnsMsgSearchTerm::MatchRfc822String(constchar*string,constchar*charset,PRBoolcharsetOverride,PRBool*pResult){NS_ENSURE_ARG_POINTER(pResult);*pResult=PR_FALSE;PRBoolresult;nsresulterr=InitHeaderAddressParser();if(NS_FAILED(err))returnerr;// 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 addresschar*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 recipientsPRBoolboolContinueLoop;GetMatchAllBeforeDeciding(&boolContinueLoop);result=boolContinueLoop;PRUint32count;nsresultparseErr=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)returnerr;nsCAutoStringwalkNames;nsCAutoStringwalkAddresses;PRInt32namePos=0;PRInt32addressPos=0;for(PRUint32i=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;returnerr;}nsresultnsMsgSearchTerm::GetLocalTimes(PRTimea,PRTimeb,PRExplodedTime&aExploded,PRExplodedTime&bExploded){PR_ExplodeTime(a,PR_LocalTimeParameters,&aExploded);PR_ExplodeTime(b,PR_LocalTimeParameters,&bExploded);returnNS_OK;}nsresultnsMsgSearchTerm::MatchDate(PRTimedateToMatch,PRBool*pResult){NS_ENSURE_ARG_POINTER(pResult);nsresulterr=NS_OK;PRBoolresult=PR_FALSE;nsTimet_date(dateToMatch);switch(m_operator){casensMsgSearchOp::IsBefore:if(t_date<nsTime(m_value.u.date))result=PR_TRUE;break;casensMsgSearchOp::IsAfter:{nsTimeadjustedDate=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;casensMsgSearchOp::Is:{PRExplodedTimetmToMatch,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;casensMsgSearchOp::Isnt:{PRExplodedTimetmToMatch,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;returnerr;}nsresultnsMsgSearchTerm::MatchAge(PRTimemsgDate,PRBool*pResult){NS_ENSURE_ARG_POINTER(pResult);PRBoolresult=PR_FALSE;nsresulterr=NS_OK;PRTimenow=PR_Now();PRTimecutOffDay;PRInt64microSecondsPerSecond,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;PRBoolcutOffDayInTheFuture=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){casensMsgSearchOp::IsGreaterThan:// is older than, or more in the futureif((!cutOffDayInTheFuture&&LL_CMP(msgDate,<,cutOffDay))||(cutOffDayInTheFuture&&LL_CMP(msgDate,>,cutOffDay)))result=PR_TRUE;break;casensMsgSearchOp::IsLessThan:// is younger than, or less in the futureif((!cutOffDayInTheFuture&&LL_CMP(msgDate,>,cutOffDay))||(cutOffDayInTheFuture&&LL_CMP(msgDate,<,cutOffDay)))result=PR_TRUE;break;casensMsgSearchOp::Is:PRExplodedTimemsgDateExploded;PRExplodedTimecutOffDayExploded;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;returnerr;}nsresultnsMsgSearchTerm::MatchSize(PRUint32sizeToMatch,PRBool*pResult){NS_ENSURE_ARG_POINTER(pResult);PRBoolresult=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.PRUint32sizeToMatchKB=sizeToMatch;if(sizeToMatchKB<1024)sizeToMatchKB=1024;sizeToMatchKB/=1024;switch(m_operator){casensMsgSearchOp::IsGreaterThan:if(sizeToMatchKB>m_value.u.size)result=PR_TRUE;break;casensMsgSearchOp::IsLessThan:if(sizeToMatchKB<m_value.u.size)result=PR_TRUE;break;casensMsgSearchOp::Is:if(sizeToMatchKB==m_value.u.size)result=PR_TRUE;break;default:break;}*pResult=result;returnNS_OK;}nsresultnsMsgSearchTerm::MatchJunkStatus(constchar*aJunkScore,PRBool*pResult){NS_ENSURE_ARG_POINTER(pResult);nsMsgJunkStatusjunkStatus;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 junkjunkStatus=nsIJunkMailPlugin::GOOD;}nsresultrv=NS_OK;PRBoolmatches=(junkStatus==m_value.u.junkStatus);switch(m_operator){casensMsgSearchOp::Is:break;casensMsgSearchOp::Isnt:matches=!matches;break;default:rv=NS_ERROR_FAILURE;NS_ASSERTION(PR_FALSE,"invalid compare op for junk status");}*pResult=matches;returnrv;}nsresultnsMsgSearchTerm::MatchJunkScoreOrigin(constchar*aJunkScoreOrigin,PRBool*pResult){NS_ENSURE_ARG_POINTER(pResult);PRBoolmatches=PR_FALSE;nsresultrv=NS_OK;switch(m_operator){casensMsgSearchOp::Is:matches=aJunkScoreOrigin&&!strcmp(aJunkScoreOrigin,m_value.string);break;casensMsgSearchOp::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;returnrv;}nsresultnsMsgSearchTerm::MatchJunkPercent(PRUint32aJunkPercent,PRBool*pResult){NS_ENSURE_ARG_POINTER(pResult);PRBoolresult=PR_FALSE;switch(m_operator){casensMsgSearchOp::IsGreaterThan:if(aJunkPercent>m_value.u.junkPercent)result=PR_TRUE;break;casensMsgSearchOp::IsLessThan:if(aJunkPercent<m_value.u.junkPercent)result=PR_TRUE;break;casensMsgSearchOp::Is:if(aJunkPercent==m_value.u.junkPercent)result=PR_TRUE;break;default:break;}*pResult=result;returnNS_OK;}nsresultnsMsgSearchTerm::MatchLabel(nsMsgLabelValueaLabelValue,PRBool*pResult){NS_ENSURE_ARG_POINTER(pResult);PRBoolresult=PR_FALSE;switch(m_operator){casensMsgSearchOp::Is:if(m_value.u.label==aLabelValue)result=PR_TRUE;break;default:if(m_value.u.label!=aLabelValue)result=PR_TRUE;break;}*pResult=result;returnNS_OK;}nsresultnsMsgSearchTerm::MatchStatus(PRUint32statusToMatch,PRBool*pResult){NS_ENSURE_ARG_POINTER(pResult);nsresultrv=NS_OK;PRBoolmatches=(statusToMatch&m_value.u.msgStatus);switch(m_operator){casensMsgSearchOp::Is:break;casensMsgSearchOp::Isnt:matches=!matches;break;default:rv=NS_ERROR_FAILURE;NS_ERROR("invalid compare op for msg status");}*pResult=matches;returnrv;}/* * 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 keywordListnsresultnsMsgSearchTerm::MatchKeyword(constnsACString&keywordList,PRBool*pResult){NS_ENSURE_ARG_POINTER(pResult);PRBoolmatches=PR_FALSE;// special-case empty for performance reasonsif(keywordList.IsEmpty()){*pResult=m_operator!=nsMsgSearchOp::Contains&&m_operator!=nsMsgSearchOp::Is&&m_operator!=nsMsgSearchOp::IsntEmpty;returnNS_OK;}// check if we can skip expensive valid keywordList testif(m_operator==nsMsgSearchOp::DoesntContain||m_operator==nsMsgSearchOp::Contains){nsCStringkeywordString(keywordList);constPRUint32kKeywordLen=PL_strlen(m_value.string);constchar*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.constchar*matchEnd=matchStart+kKeywordLen;if((matchStart==keywordString.get()||matchStart[-1]==' ')&&(!*matchEnd||*matchEnd==' ')){// found the keyword*pResult=m_operator==nsMsgSearchOp::Contains;returnNS_OK;}// no match yet, so search onmatchStart=PL_strstr(matchEnd,m_value.string);}// keyword not found*pResult=m_operator==nsMsgSearchOp::DoesntContain;returnNS_OK;}// Only accept valid keys in tokens.nsresultrv=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 keywordsPRUint32count=keywordArray.Length();for(PRUint32i=0;i<count;i++){// is this token a valid tag? Otherwise ignore itPRBoolisValid;rv=tagService->IsValidKey(keywordArray[i],&isValid);NS_ENSURE_SUCCESS(rv,rv);if(isValid){// IsEmpty fails on any valid tokenif(m_operator==nsMsgSearchOp::IsEmpty){*pResult=PR_FALSE;returnrv;}// IsntEmpty succeeds on any valid tokenif(m_operator==nsMsgSearchOp::IsntEmpty){*pResult=PR_TRUE;returnrv;}// 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 tokenif(!matches){if(m_operator==nsMsgSearchOp::Is){*pResult=PR_FALSE;returnrv;}if(m_operator==nsMsgSearchOp::Isnt){*pResult=PR_TRUE;returnrv;}}}}if(m_operator==nsMsgSearchOp::Is){*pResult=matches;returnNS_OK;}if(m_operator==nsMsgSearchOp::Isnt){*pResult=!matches;returnNS_OK;}if(m_operator==nsMsgSearchOp::IsEmpty){*pResult=PR_TRUE;returnNS_OK;}if(m_operator==nsMsgSearchOp::IsntEmpty){*pResult=PR_FALSE;returnNS_OK;}// no valid match operator foundNS_ERROR("invalid compare op for msg status");returnNS_ERROR_FAILURE;}nsresultnsMsgSearchTerm::MatchPriority(nsMsgPriorityValuepriorityToMatch,PRBool*pResult){NS_ENSURE_ARG_POINTER(pResult);nsresulterr=NS_OK;PRBoolresult=NS_OK;// Use this ugly little hack to get around the fact that enums don't have// integer compare operatorsintp1=(priorityToMatch==nsMsgPriority::none)?(int)nsMsgPriority::normal:(int)priorityToMatch;intp2=(int)m_value.u.priority;switch(m_operator){casensMsgSearchOp::IsHigherThan:if(p1>p2)result=PR_TRUE;break;casensMsgSearchOp::IsLowerThan:if(p1<p2)result=PR_TRUE;break;casensMsgSearchOp::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;returnerr;}// Lazily initialize the rfc822 header parser we're going to use to do// header matching.nsresultnsMsgSearchTerm::InitHeaderAddressParser(){nsresultres=NS_OK;if(!m_headerAddressParser){m_headerAddressParser=do_GetService(NS_MAILNEWS_MIME_HEADER_PARSER_CONTRACTID,&res);}returnres;}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_IMETHODIMPnsMsgSearchTerm::GetValue(nsIMsgSearchValue**aResult){NS_ENSURE_ARG_POINTER(aResult);*aResult=newnsMsgSearchValueImpl(&m_value);NS_IF_ADDREF(*aResult);returnNS_OK;}NS_IMETHODIMPnsMsgSearchTerm::SetValue(nsIMsgSearchValue*aValue){nsMsgResultElement::AssignValues(aValue,&m_value);returnNS_OK;}NS_IMETHODIMPnsMsgSearchTerm::GetBooleanAnd(PRBool*aResult){NS_ENSURE_ARG_POINTER(aResult);*aResult=(m_booleanOp==nsMsgSearchBooleanOp::BooleanAND);returnNS_OK;}NS_IMETHODIMPnsMsgSearchTerm::SetBooleanAnd(PRBoolaValue){if(aValue)m_booleanOp=nsMsgSearchBooleanOperator(nsMsgSearchBooleanOp::BooleanAND);elsem_booleanOp=nsMsgSearchBooleanOperator(nsMsgSearchBooleanOp::BooleanOR);returnNS_OK;}NS_IMETHODIMPnsMsgSearchTerm::GetArbitraryHeader(nsACString&aResult){aResult=m_arbitraryHeader;returnNS_OK;}NS_IMETHODIMPnsMsgSearchTerm::SetArbitraryHeader(constnsACString&aValue){m_arbitraryHeader=aValue;ToLowerCaseExceptSpecials(m_arbitraryHeader);returnNS_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.//voidnsMsgSearchTerm::ToLowerCaseExceptSpecials(nsACString&aValue){#ifdef MOZILLA_INTERNAL_APIif(NS_LITERAL_CSTRING("Sender").Equals(aValue,nsCaseInsensitiveCStringComparator()))aValue.Assign(NS_LITERAL_CSTRING("Sender"));elseif(NS_LITERAL_CSTRING("Date").Equals(aValue,nsCaseInsensitiveCStringComparator()))aValue.Assign(NS_LITERAL_CSTRING("Date"));elseif(NS_LITERAL_CSTRING("Status").Equals(aValue,nsCaseInsensitiveCStringComparator()))aValue.Assign(NS_LITERAL_CSTRING("Status"));#elseif(NS_LITERAL_CSTRING("Sender").Equals(aValue,CaseInsensitiveCompare))aValue.Assign(NS_LITERAL_CSTRING("Sender"));elseif(NS_LITERAL_CSTRING("Date").Equals(aValue,CaseInsensitiveCompare))aValue.Assign(NS_LITERAL_CSTRING("Date"));elseif(NS_LITERAL_CSTRING("Status").Equals(aValue,CaseInsensitiveCompare))aValue.Assign(NS_LITERAL_CSTRING("Status"));#endifelseToLowerCase(aValue);}//-----------------------------------------------------------------------------// nsMsgSearchScopeTerm implementation//-----------------------------------------------------------------------------nsMsgSearchScopeTerm::nsMsgSearchScopeTerm(nsIMsgSearchSession*session,nsMsgSearchScopeValueattribute,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_IMETHODIMPnsMsgSearchScopeTerm::GetFolder(nsIMsgFolder**aResult){NS_IF_ADDREF(*aResult=m_folder);returnNS_OK;}NS_IMETHODIMPnsMsgSearchScopeTerm::GetSearchSession(nsIMsgSearchSession**aResult){NS_ENSURE_ARG_POINTER(aResult);nsCOMPtr<nsIMsgSearchSession>searchSession=do_QueryReferent(m_searchSession);NS_IF_ADDREF(*aResult=searchSession);returnNS_OK;}NS_IMETHODIMPnsMsgSearchScopeTerm::GetMailFile(nsILocalFile**aLocalFile){NS_ENSURE_ARG_POINTER(aLocalFile);if(!m_localFile){if(!m_folder)returnNS_ERROR_NULL_POINTER;m_folder->GetFilePath(getter_AddRefs(m_localFile));}if(m_localFile){NS_ADDREF(*aLocalFile=m_localFile);returnNS_OK;}returnNS_ERROR_FAILURE;}NS_IMETHODIMPnsMsgSearchScopeTerm::GetInputStream(nsIInputStream**aInputStream){NS_ENSURE_ARG_POINTER(aInputStream);nsresultrv=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 messagesm_inputStream=do_QueryInterface(fileStream);}NS_IF_ADDREF(*aInputStream=m_inputStream);returnrv;}NS_IMETHODIMPnsMsgSearchScopeTerm::SetInputStream(nsIInputStream*aInputStream){if(!aInputStream&&m_inputStream)m_inputStream->Close();m_inputStream=aInputStream;returnNS_OK;}nsresultnsMsgSearchScopeTerm::TimeSlice(PRBool*aDone){returnm_adapter->Search(aDone);}nsresultnsMsgSearchScopeTerm::InitializeAdapter(nsISupportsArray*termList){if(m_adapter)returnNS_OK;nsresulterr=NS_OK;switch(m_attribute){casensMsgSearchScope::onlineMail:m_adapter=newnsMsgSearchOnlineMail(this,termList);break;casensMsgSearchScope::offlineMail:m_adapter=newnsMsgSearchOfflineMail(this,termList);break;casensMsgSearchScope::newsEx:NS_ASSERTION(PR_FALSE,"not supporting newsEx yet");break;casensMsgSearchScope::news:m_adapter=newnsMsgSearchNews(this,termList);break;casensMsgSearchScope::allSearchableGroups:NS_ASSERTION(PR_FALSE,"not supporting allSearchableGroups yet");break;casensMsgSearchScope::LDAP:NS_ASSERTION(PR_FALSE,"not supporting LDAP yet");break;casensMsgSearchScope::localNews:m_adapter=newnsMsgSearchOfflineNews(this,termList);break;default:NS_ASSERTION(PR_FALSE,"invalid scope");err=NS_ERROR_FAILURE;}if(m_adapter)err=m_adapter->ValidateTerms();returnerr;}char*nsMsgSearchScopeTerm::GetStatusBarName(){returnnsnull;}//-----------------------------------------------------------------------------// nsMsgResultElement implementation//-----------------------------------------------------------------------------nsMsgResultElement::nsMsgResultElement(nsIMsgSearchAdapter*adapter){NS_NewISupportsArray(getter_AddRefs(m_valueList));m_adapter=adapter;}nsMsgResultElement::~nsMsgResultElement(){}nsresultnsMsgResultElement::AddValue(nsIMsgSearchValue*value){m_valueList->AppendElement(value);returnNS_OK;}nsresultnsMsgResultElement::AddValue(nsMsgSearchValue*value){nsMsgSearchValueImpl*valueImpl=newnsMsgSearchValueImpl(value);deletevalue;// we keep the nsIMsgSearchValue, not// the nsMsgSearchValuereturnAddValue(valueImpl);}nsresultnsMsgResultElement::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 muchnsresulterr=NS_OK;src->GetAttrib(&dst->attribute);switch(dst->attribute){casensMsgSearchAttrib::Priority:err=src->GetPriority(&dst->u.priority);break;casensMsgSearchAttrib::Date:err=src->GetDate(&dst->u.date);break;casensMsgSearchAttrib::HasAttachmentStatus:casensMsgSearchAttrib::MsgStatus:err=src->GetStatus(&dst->u.msgStatus);break;casensMsgSearchAttrib::MessageKey:err=src->GetMsgKey(&dst->u.key);break;casensMsgSearchAttrib::AgeInDays:err=src->GetAge(&dst->u.age);break;casensMsgSearchAttrib::Label:err=src->GetLabel(&dst->u.label);break;casensMsgSearchAttrib::JunkStatus:err=src->GetJunkStatus(&dst->u.junkStatus);break;casensMsgSearchAttrib::JunkPercent:err=src->GetJunkPercent(&dst->u.junkPercent);break;casensMsgSearchAttrib::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");nsStringunicodeString;err=src->GetStr(unicodeString);dst->string=ToNewUTF8String(unicodeString);}elseerr=NS_ERROR_INVALID_ARG;}returnerr;}nsresultnsMsgResultElement::GetValue(nsMsgSearchAttribValueattrib,nsMsgSearchValue**outValue)const{nsresulterr=NS_OK;nsCOMPtr<nsIMsgSearchValue>value;*outValue=NULL;PRUint32count;m_valueList->Count(&count);for(PRUint32i=0;i<count&&err!=NS_OK;i++){m_valueList->QueryElementAt(i,NS_GET_IID(nsIMsgSearchValue),(void**)getter_AddRefs(value));nsMsgSearchAttribValuevalueAttribute;value->GetAttrib(&valueAttribute);if(attrib==valueAttribute){*outValue=newnsMsgSearchValue;if(*outValue){err=AssignValues(value,*outValue);err=NS_OK;}elseerr=NS_ERROR_OUT_OF_MEMORY;}}returnerr;}nsresultnsMsgResultElement::GetPrettyName(nsMsgSearchValue**value){returnGetValue(nsMsgSearchAttrib::Location,value);}nsresultnsMsgResultElement::Open(void*window){returnNS_ERROR_NULL_POINTER;}