mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-05 08:35:26 +00:00
5327 lines
147 KiB
C++
5327 lines
147 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
*
|
|
* The contents of this file are subject to the Netscape Public
|
|
* License Version 1.1 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS
|
|
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
* implied. See the License for the specific language governing
|
|
* rights and limitations under the License.
|
|
*
|
|
* The Original Code is mozilla.org code.
|
|
*
|
|
* The Initial Developer of the Original Code is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All
|
|
* Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
*/
|
|
// Implementation of search API for mail and news
|
|
//
|
|
//
|
|
#include "msg.h"
|
|
#include "net.h"
|
|
#include "libi18n.h"
|
|
#include "xpgetstr.h"
|
|
#include "msgstrob.h"
|
|
#include "msgprefs.h"
|
|
#include "mailhdr.h"
|
|
#include "newshdr.h"
|
|
#include "intl_csi.h"
|
|
#include "ldap.h"
|
|
|
|
|
|
extern "C"
|
|
{
|
|
extern int MK_OUT_OF_MEMORY;
|
|
extern int MK_UNABLE_TO_OPEN_FILE;
|
|
|
|
extern int XP_SEARCH_SUBJECT;
|
|
extern int XP_SEARCH_CONTAINS;
|
|
extern int XP_SEARCH_AGE; // mscott: hack for searching by age...
|
|
extern int XP_SEARCH_IS_GREATER;
|
|
extern int XP_SEARCH_IS_LESS; // for searching by age
|
|
|
|
extern int XP_STATUS_READ;
|
|
extern int XP_STATUS_REPLIED;
|
|
extern int XP_STATUS_FORWARDED;
|
|
extern int XP_STATUS_REPLIED_AND_FORWARDED;
|
|
extern int XP_STATUS_NEW;
|
|
|
|
extern int MK_MSG_SEARCH_STATUS;
|
|
|
|
extern int MK_SEARCH_HITS_COUNTER;
|
|
|
|
extern int MK_SEARCH_SCOPE_ONE;
|
|
extern int MK_SEARCH_SCOPE_SELECTED;
|
|
extern int MK_SEARCH_SCOPE_OFFLINE_MAIL;
|
|
extern int MK_SEARCH_SCOPE_ONLINE_MAIL;
|
|
extern int MK_SEARCH_SCOPE_SUBSCRIBED_NEWS;
|
|
extern int MK_SEARCH_SCOPE_ALL_NEWS;
|
|
|
|
extern int XP_PRIORITY_NONE;
|
|
extern int XP_PRIORITY_LOWEST;
|
|
extern int XP_PRIORITY_LOW;
|
|
extern int XP_PRIORITY_NORMAL;
|
|
extern int XP_PRIORITY_HIGH;
|
|
extern int XP_PRIORITY_HIGHEST;
|
|
|
|
extern int XP_SEARCH_VALUE_REQUIRED;
|
|
}
|
|
|
|
#include "pmsgsrch.h" // private search API
|
|
#include "msgmast.h"
|
|
#include "msgfinfo.h"
|
|
#include "xp_time.h" // for XP_LocalZoneOffset
|
|
#include "dirprefs.h"
|
|
#include "msgmpane.h" // for LoadMessage
|
|
#include "newshost.h" // for QueryExtension
|
|
#include "hosttbl.h" // for AddAllScopes
|
|
#include "msgurlq.h" // for MSG_UrlQueue
|
|
#include "prefapi.h"
|
|
|
|
#include "msgimap.h" // hopefully just temporary..
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// ------------------------ Public API implementation -------------------------
|
|
//-----------------------------------------------------------------------------
|
|
SEARCH_API MSG_SearchError MSG_SearchAlloc (MSG_Pane *pane)
|
|
{
|
|
// Make sure there isn't another allocated searchFrame in this context
|
|
XP_ASSERT (pane);
|
|
XP_ASSERT (NULL == pane->GetContext()->msg_searchFrame);
|
|
|
|
// Make sure pane really is a linedPane
|
|
MSG_PaneType type = pane->GetPaneType();
|
|
XP_ASSERT (MSG_ADDRPANE == type || AB_ABPANE == type || MSG_SEARCHPANE == type || type == AB_PICKERPANE);
|
|
|
|
MSG_SearchFrame *frame = new MSG_SearchFrame ((MSG_LinedPane*) pane);
|
|
return frame ? SearchError_Success : SearchError_OutOfMemory;
|
|
}
|
|
|
|
|
|
SEARCH_API MSG_SearchError MSG_SearchFree (MSG_Pane *pane)
|
|
{
|
|
MSG_SearchFrame *frame = MSG_SearchFrame::FromPane (pane);
|
|
if (NULL != frame)
|
|
{
|
|
delete frame;
|
|
pane->GetContext()->msg_searchFrame = NULL;
|
|
return SearchError_Success;
|
|
}
|
|
return SearchError_NullPointer;
|
|
}
|
|
|
|
SEARCH_API MSG_SearchError MSG_AddSearchTerm (MSG_Pane *pane,
|
|
MSG_SearchAttribute attrib, MSG_SearchOperator op, MSG_SearchValue *value, XP_Bool BooleanAND, char * arbitraryHeader)
|
|
{
|
|
MSG_SearchFrame *frame = MSG_SearchFrame::FromPane (pane);
|
|
if (!frame)
|
|
return SearchError_InvalidPane;
|
|
return frame->AddSearchTerm (attrib, op, value, BooleanAND, arbitraryHeader);
|
|
}
|
|
|
|
MSG_SearchError MSG_CountSearchTerms (MSG_Pane *pane, int *numTerms)
|
|
{
|
|
MSG_SearchFrame *frame = MSG_SearchFrame::FromPane (pane);
|
|
if (!frame)
|
|
return SearchError_InvalidPane;
|
|
if (!numTerms)
|
|
return SearchError_NullPointer;
|
|
*numTerms = frame->m_termList.GetSize();
|
|
return SearchError_Success;
|
|
}
|
|
|
|
MSG_SearchError MSG_GetNthSearchTerm (MSG_Pane *pane, int whichTerm,
|
|
MSG_SearchAttribute *attrib, MSG_SearchOperator *op, MSG_SearchValue *value)
|
|
{
|
|
MSG_SearchFrame *frame = MSG_SearchFrame::FromPane (pane);
|
|
if (!frame)
|
|
return SearchError_InvalidPane;
|
|
if (!attrib || !op || !value)
|
|
return SearchError_NullPointer;
|
|
|
|
MSG_SearchTerm *term = frame->m_termList.GetAt(whichTerm);
|
|
*attrib = term->m_attribute;
|
|
*op = term->m_operator;
|
|
return MSG_ResultElement::AssignValues (&term->m_value, value);
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_CountSearchScopes (MSG_Pane *pane, int *numScopes)
|
|
{
|
|
MSG_SearchFrame *frame = MSG_SearchFrame::FromPane (pane);
|
|
if (!frame)
|
|
return SearchError_InvalidPane;
|
|
if (!numScopes)
|
|
return SearchError_NullPointer;
|
|
|
|
*numScopes = frame->m_scopeList.GetSize();
|
|
return SearchError_Success;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_GetNthSearchScope (MSG_Pane *pane, int which, MSG_ScopeAttribute *scopeId, void **scope)
|
|
{
|
|
MSG_SearchFrame *frame = MSG_SearchFrame::FromPane (pane);
|
|
if (!frame)
|
|
return SearchError_InvalidPane;
|
|
if (!scopeId || !scope)
|
|
return SearchError_NullPointer;
|
|
|
|
MSG_ScopeTerm *scopeTerm = frame->m_scopeList.GetAt(which);
|
|
*scopeId = scopeTerm->m_attribute;
|
|
if (*scopeId == scopeLdapDirectory)
|
|
*scope = scopeTerm->m_server;
|
|
else
|
|
*scope = scopeTerm->m_folder;
|
|
return SearchError_Success;
|
|
}
|
|
|
|
|
|
SEARCH_API MSG_SearchError MSG_AddScopeTerm (MSG_Pane *pane, MSG_ScopeAttribute attrib, MSG_FolderInfo *folder)
|
|
{
|
|
MSG_SearchFrame *frame = MSG_SearchFrame::FromPane (pane);
|
|
if (!frame)
|
|
return SearchError_InvalidPane;
|
|
|
|
// do not do a deep search starting from the IMAP INBOX
|
|
XP_Bool deep = !((folder->GetType() == FOLDER_IMAPMAIL) && (folder->GetFlags() & MSG_FOLDER_FLAG_INBOX));
|
|
return frame->AddScopeTree (attrib, folder, deep);
|
|
}
|
|
|
|
|
|
SEARCH_API MSG_SearchError MSG_AddAllScopes (MSG_Pane *pane, MSG_Master *master, MSG_ScopeAttribute scope)
|
|
{
|
|
XP_ASSERT (master);
|
|
|
|
MSG_SearchFrame *frame = MSG_SearchFrame::FromPane (pane);
|
|
if (!frame)
|
|
return SearchError_InvalidPane;
|
|
if (!master)
|
|
return SearchError_NullPointer;
|
|
return frame->AddAllScopes (master, scope);
|
|
}
|
|
|
|
|
|
SEARCH_API MSG_SearchError MSG_AddLdapScope (MSG_Pane *pane, DIR_Server *server)
|
|
{
|
|
XP_ASSERT (server);
|
|
|
|
MSG_SearchFrame *frame = MSG_SearchFrame::FromPane (pane);
|
|
if (!frame)
|
|
return SearchError_InvalidPane;
|
|
return frame->AddScopeTerm (server);
|
|
}
|
|
|
|
|
|
SEARCH_API MSG_SearchError MSG_AddAllLdapScopes (MSG_Pane *pane, XP_List *dirServerList)
|
|
{
|
|
// This is a convenience function which doesn't do anything the FE couldn't do themselves
|
|
XP_ASSERT (dirServerList);
|
|
XP_ASSERT (!XP_ListIsEmpty(dirServerList));
|
|
|
|
MSG_SearchError err = SearchError_Success;
|
|
DIR_Server *server;
|
|
for (int i = 1; i < XP_ListCount(dirServerList); i++)
|
|
{
|
|
server = (DIR_Server*) XP_ListGetObjectNum (dirServerList, i);
|
|
err = MSG_AddLdapScope (pane, server);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
|
|
SEARCH_API MSG_SearchError MSG_Search (MSG_Pane *pane)
|
|
{
|
|
MSG_SearchFrame *frame = MSG_SearchFrame::FromPane (pane);
|
|
if (!frame)
|
|
return SearchError_InvalidPane;
|
|
|
|
MSG_SearchError err = frame->Initialize ();
|
|
if (SearchError_Success == err)
|
|
err = frame->BeginSearching ();
|
|
else
|
|
{
|
|
if (SearchError_ValueRequired == err)
|
|
FE_Alert (pane->GetContext(), XP_GetString (XP_SEARCH_VALUE_REQUIRED));
|
|
}
|
|
return err;
|
|
}
|
|
|
|
SEARCH_API MSG_SearchError MSG_InterruptSearchViaPane (MSG_Pane *pane)
|
|
{
|
|
MSG_SearchFrame *frame = MSG_SearchFrame::FromPane (pane);
|
|
if (!frame)
|
|
return SearchError_InvalidPane;
|
|
|
|
if (frame->m_handlingError)
|
|
return SearchError_Busy;
|
|
|
|
if (pane)
|
|
pane->InterruptContext (TRUE);
|
|
|
|
return SearchError_Success;
|
|
}
|
|
|
|
|
|
SEARCH_API MSG_SearchError MSG_SetSearchParam (MSG_Pane *pane, MSG_SearchType type, void *param)
|
|
{
|
|
MSG_SearchFrame *frame = MSG_SearchFrame::FromPane (pane);
|
|
if (!frame)
|
|
return SearchError_InvalidPane;
|
|
|
|
return frame->SetSearchParam(type, param);
|
|
}
|
|
|
|
SEARCH_API void *MSG_GetSearchParam (MSG_Pane *pane)
|
|
{
|
|
MSG_SearchFrame *frame = MSG_SearchFrame::FromPane (pane);
|
|
if (frame)
|
|
return frame->GetSearchParam();
|
|
|
|
return NULL;
|
|
}
|
|
|
|
SEARCH_API MSG_SearchType MSG_GetSearchType (MSG_Pane *pane)
|
|
{
|
|
MSG_SearchFrame *frame = MSG_SearchFrame::FromPane (pane);
|
|
if (frame)
|
|
return frame->GetSearchType();
|
|
|
|
return searchNone;
|
|
}
|
|
|
|
SEARCH_API uint32 MSG_GetNumResults (MSG_Pane *pane)
|
|
{
|
|
MSG_SearchFrame *frame = MSG_SearchFrame::FromPane (pane);
|
|
if (frame)
|
|
return frame->m_resultList.GetSize();
|
|
|
|
return 0;
|
|
}
|
|
|
|
SEARCH_API MSG_SearchError MSG_GetResultElement (MSG_Pane *pane, MSG_ViewIndex idx, MSG_ResultElement **result)
|
|
{
|
|
XP_ASSERT(pane);
|
|
XP_ASSERT(result);
|
|
|
|
MSG_SearchFrame *frame = MSG_SearchFrame::FromPane (pane);
|
|
if (!frame)
|
|
return SearchError_InvalidPane;
|
|
return frame->GetResultElement (idx, result);
|
|
}
|
|
|
|
|
|
SEARCH_API MSG_SearchError MSG_GetResultAttribute (
|
|
MSG_ResultElement *elem, MSG_SearchAttribute attr, MSG_SearchValue **value)
|
|
{
|
|
XP_ASSERT(elem->IsValid());
|
|
if (attribLocation == attr)
|
|
return elem->GetPrettyName (value);
|
|
return elem->GetValue (attr, value);
|
|
}
|
|
|
|
SEARCH_API MSG_SearchError MSG_DestroySearchValue (MSG_SearchValue *value)
|
|
{
|
|
XP_ASSERT(value);
|
|
return MSG_ResultElement::DestroyValue (value);
|
|
}
|
|
|
|
|
|
SEARCH_API MSG_SearchError MSG_OpenResultElement (MSG_ResultElement *elem, void *window)
|
|
{
|
|
XP_ASSERT(elem && elem->IsValid());
|
|
return elem->Open (window);
|
|
}
|
|
|
|
|
|
SEARCH_API MWContextType MSG_GetResultElementType (MSG_ResultElement *elem)
|
|
{
|
|
XP_ASSERT(elem->IsValid());
|
|
return elem->GetContextType ();
|
|
}
|
|
|
|
|
|
SEARCH_API MWContext *MSG_IsResultElementOpen (MSG_ResultElement * /*elem*/)
|
|
{
|
|
return NULL; //###phil hack for now
|
|
}
|
|
|
|
|
|
SEARCH_API MSG_SearchError MSG_ModifyLdapResult (MSG_ResultElement *elem, MSG_SearchValue *val)
|
|
{
|
|
XP_ASSERT(elem && elem->IsValid());
|
|
XP_ASSERT(val);
|
|
XP_ASSERT(elem->m_adapter);
|
|
return elem->m_adapter->ModifyResultElement (elem, val);
|
|
}
|
|
|
|
|
|
SEARCH_API MSG_SearchError MSG_AddLdapResultsToAddressBook(MSG_Pane *pane, MSG_ViewIndex *indices, int count)
|
|
{
|
|
XP_ASSERT (pane);
|
|
XP_ASSERT (indices);
|
|
MSG_SearchFrame *frame = MSG_SearchFrame::FromPane (pane);
|
|
if (!frame)
|
|
return SearchError_InvalidPane;
|
|
return frame->AddLdapResultsToAddressBook (indices, count);
|
|
}
|
|
|
|
|
|
SEARCH_API MSG_SearchError MSG_ComposeFromLdapResults(MSG_Pane *pane, MSG_ViewIndex *indices, int count)
|
|
{
|
|
XP_ASSERT (pane);
|
|
XP_ASSERT (indices);
|
|
MSG_SearchFrame *frame = MSG_SearchFrame::FromPane (pane);
|
|
if (!frame)
|
|
return SearchError_InvalidPane;
|
|
return frame->ComposeFromLdapResults (indices, count);
|
|
}
|
|
|
|
|
|
SEARCH_API MSG_SearchError MSG_SortResultList (MSG_Pane *pane, MSG_SearchAttribute attrib, XP_Bool descending)
|
|
{
|
|
MSG_SearchFrame *frame = MSG_SearchFrame::FromPane (pane);
|
|
if (!frame)
|
|
return SearchError_InvalidPane;
|
|
return frame->SortResultList (attrib, descending);
|
|
}
|
|
|
|
|
|
SEARCH_API XP_Bool MSG_GoToFolderStatus (MSG_Pane *pane, MSG_ViewIndex *indices, int32 numIndices)
|
|
{
|
|
MSG_SearchFrame *frame = MSG_SearchFrame::FromPane (pane);
|
|
if (!frame)
|
|
return SearchError_InvalidPane;
|
|
return frame->GetGoToFolderStatus (indices, numIndices);
|
|
}
|
|
|
|
|
|
typedef struct
|
|
{
|
|
MSG_SearchAttribute attrib;
|
|
const char *attribName;
|
|
} SearchAttribEntry;
|
|
|
|
SearchAttribEntry SearchAttribEntryTable[] =
|
|
{
|
|
{attribSubject, "subject"},
|
|
{attribSender, "from"},
|
|
{attribBody, "body"},
|
|
{attribDate, "date"},
|
|
{attribPriority, "priority"},
|
|
{attribMsgStatus, "status"},
|
|
{attribTo, "to"},
|
|
{attribCC, "CC"},
|
|
{attribToOrCC, "to or CC"}
|
|
};
|
|
|
|
#if defined(XP_MAC) && defined (__MWERKS__)
|
|
#pragma require_prototypes off
|
|
#endif
|
|
|
|
// 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
|
|
SEARCH_API MSG_SearchError MSG_GetAttributeFromString(const char *string, int16 *attrib)
|
|
{
|
|
if (NULL == string || NULL == attrib)
|
|
return SearchError_NullPointer;
|
|
XP_Bool found = FALSE;
|
|
for (int idxAttrib = 0; idxAttrib < sizeof(SearchAttribEntryTable) / sizeof(SearchAttribEntry); idxAttrib++)
|
|
{
|
|
if (!XP_STRCASECMP(string, SearchAttribEntryTable[idxAttrib].attribName))
|
|
{
|
|
found = TRUE;
|
|
*attrib = SearchAttribEntryTable[idxAttrib].attrib;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
*attrib = attribOtherHeader; // assume arbitrary header if we could not find the header in the table
|
|
return SearchError_Success; // we always succeed now
|
|
}
|
|
|
|
SEARCH_API MSG_SearchError MSG_GetStringForAttribute(int16 attrib, const char **string)
|
|
{
|
|
if (NULL == string)
|
|
return SearchError_NullPointer;
|
|
XP_Bool found = FALSE;
|
|
for (int idxAttrib = 0; idxAttrib < sizeof(SearchAttribEntryTable) / sizeof(SearchAttribEntry); idxAttrib++)
|
|
{
|
|
// I'm using the idx's as aliases into MSG_SearchAttribute and
|
|
// MSG_SearchOperator enums which is legal because of the way the
|
|
// enums are defined (starts at 0, numItems at end)
|
|
if (attrib == SearchAttribEntryTable[idxAttrib].attrib)
|
|
{
|
|
found = TRUE;
|
|
*string = SearchAttribEntryTable[idxAttrib].attribName;
|
|
break;
|
|
}
|
|
}
|
|
// we no longer return invalid attribute. If we cannot find the string in the table,
|
|
// then it is an arbitrary header. Return success regardless if found or not
|
|
// return (found) ? SearchError_Success : SearchError_InvalidAttribute;
|
|
return SearchError_Success;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
MSG_SearchOperator op;
|
|
const char *opName;
|
|
} SearchOperatorEntry;
|
|
|
|
SearchOperatorEntry SearchOperatorEntryTable[] =
|
|
{
|
|
{opContains, "contains"},
|
|
{opDoesntContain,"doesn't contain"},
|
|
{opIs, "is"},
|
|
{opIsnt, "isn't"},
|
|
{opIsEmpty, "is empty"},
|
|
{opIsBefore, "is before"},
|
|
{opIsAfter, "is after"},
|
|
{opIsHigherThan, "is higher than"},
|
|
{opIsLowerThan, "is lower than"},
|
|
{opBeginsWith, "begins with"},
|
|
{opEndsWith, "ends with"}
|
|
};
|
|
|
|
SEARCH_API MSG_SearchError MSG_GetOperatorFromString(const char *string, int16 *op)
|
|
{
|
|
if (NULL == string || NULL == op)
|
|
return SearchError_NullPointer;
|
|
|
|
XP_Bool found = FALSE;
|
|
for (int idxOp = 0; idxOp < sizeof(SearchOperatorEntryTable) / sizeof(SearchOperatorEntry); 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 (!XP_STRCASECMP(string, SearchOperatorEntryTable[idxOp].opName))
|
|
{
|
|
found = TRUE;
|
|
*op = SearchOperatorEntryTable[idxOp].op;
|
|
break;
|
|
}
|
|
}
|
|
return (found) ? SearchError_Success : SearchError_InvalidOperator;
|
|
}
|
|
|
|
SEARCH_API MSG_SearchError MSG_GetStringForOperator(int16 op, const char **string)
|
|
{
|
|
if (NULL == string)
|
|
return SearchError_NullPointer;
|
|
XP_Bool found = FALSE;
|
|
for (int idxOp = 0; idxOp < sizeof(SearchOperatorEntryTable) / sizeof(SearchOperatorEntry); idxOp++)
|
|
{
|
|
// I'm using the idx's as aliases into MSG_SearchAttribute and
|
|
// MSG_SearchOperator enums which is legal because of the way the
|
|
// enums are defined (starts at 0, numItems at end)
|
|
if (op == SearchOperatorEntryTable[idxOp].op)
|
|
{
|
|
found = TRUE;
|
|
*string = SearchOperatorEntryTable[idxOp].opName;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return (found) ? SearchError_Success : SearchError_InvalidOperator;
|
|
}
|
|
|
|
static MSG_SearchError msg_GetValidityTableForFolders (MSG_Master *master, MSG_FolderInfo **folderArray, uint16 /* numFolders */, msg_SearchValidityTable **table, XP_Bool forFilters)
|
|
{
|
|
MSG_SearchError err = SearchError_Success;
|
|
|
|
MSG_FolderInfo *folder = folderArray[0];
|
|
|
|
// determine if the user wants us to search locally or search the server by checking the pref...
|
|
// remember: this pref onlines applies for online imap and online news folders...(filters are left alone)
|
|
XP_Bool searchServer = master->GetPrefs()->GetSearchServer();
|
|
searchServer = forFilters ? TRUE : searchServer; // small hack to simplify the if statements...
|
|
|
|
|
|
if (folder->GetMailFolderInfo())
|
|
{
|
|
if (folder->GetType() == FOLDER_IMAPMAIL && searchServer)
|
|
{
|
|
if (forFilters)
|
|
err = gValidityMgr.GetTable (msg_SearchValidityManager::onlineMailFilter, table);
|
|
else
|
|
err = gValidityMgr.GetTable (msg_SearchValidityManager::onlineMail, table);
|
|
}
|
|
else
|
|
err = gValidityMgr.GetTable (msg_SearchValidityManager::offlineMail, table);
|
|
}
|
|
else if (folder->IsNews())
|
|
{
|
|
MSG_FolderInfoNews *newsFolder = (MSG_FolderInfoNews*) folder;
|
|
if (!searchServer || NET_IsOffline())
|
|
err = gValidityMgr.GetTable(msg_SearchValidityManager::localNews, table);
|
|
else
|
|
{
|
|
if (newsFolder->KnowsSearchNntpExtension())
|
|
{
|
|
err = gValidityMgr.GetTable (msg_SearchValidityManager::newsEx, table);
|
|
if (SearchError_Success == err)
|
|
err = gValidityMgr.PostProcessValidityTable (newsFolder->GetHost());
|
|
}
|
|
else
|
|
err = gValidityMgr.GetTable (msg_SearchValidityManager::news, table);
|
|
}
|
|
|
|
}
|
|
else if (FOLDER_CONTAINERONLY == folder->GetType())
|
|
{
|
|
if (!searchServer || NET_IsOffline())
|
|
err = gValidityMgr.GetTable (msg_SearchValidityManager::localNews, table);
|
|
else
|
|
{
|
|
if (folder->KnowsSearchNntpExtension())
|
|
{
|
|
MSG_NewsHost *host = master->GetHostTable()->FindNewsHost(folder);
|
|
err = gValidityMgr.GetTable (msg_SearchValidityManager::newsEx, table);
|
|
if (SearchError_Success == err)
|
|
err = gValidityMgr.PostProcessValidityTable (host);
|
|
}
|
|
else
|
|
err = gValidityMgr.GetTable (msg_SearchValidityManager::news, table);
|
|
}
|
|
}
|
|
else if (FOLDER_IMAPSERVERCONTAINER == folder->GetType())
|
|
{
|
|
if (forFilters)
|
|
err = gValidityMgr.GetTable (msg_SearchValidityManager::onlineMailFilter, table);
|
|
else
|
|
{
|
|
if (searchServer)
|
|
err = gValidityMgr.GetTable (msg_SearchValidityManager::onlineMail, table);
|
|
else
|
|
err = gValidityMgr.GetTable (msg_SearchValidityManager::offlineMail, table);
|
|
}
|
|
}
|
|
else
|
|
err = SearchError_InvalidScope;
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
static MSG_SearchError msg_GetNumAttributesForScopes (
|
|
MSG_Master *master,
|
|
MSG_ScopeAttribute scope,
|
|
void ** array,
|
|
uint16 numFolders,
|
|
uint16 *numItems,
|
|
XP_Bool forFilters)
|
|
{
|
|
if (array == NULL)
|
|
return SearchError_NullPointer;
|
|
|
|
MSG_SearchError err = SearchError_Success;
|
|
msg_SearchValidityTable *table = NULL;
|
|
|
|
DIR_Server *server = NULL;
|
|
MSG_FolderInfo **folderArray = NULL;
|
|
|
|
if (scope != scopeLdapDirectory)
|
|
{
|
|
folderArray = (MSG_FolderInfo**) array;
|
|
err = msg_GetValidityTableForFolders (master, folderArray, numFolders, &table, forFilters);
|
|
}
|
|
else
|
|
{
|
|
server = *(DIR_Server**) array;
|
|
XP_ASSERT(numFolders == 1);
|
|
if (numFolders > 1)
|
|
return SearchError_ScopeAgreement;
|
|
err = gValidityMgr.GetTable (msg_SearchValidityManager::Ldap, &table);
|
|
if (SearchError_Success == err)
|
|
gValidityMgr.PostProcessValidityTable (server);
|
|
}
|
|
|
|
*numItems = 0;
|
|
if (SearchError_Success == err)
|
|
*numItems = table->GetNumAvailAttribs() + master->GetPrefs()->GetNumCustomHeaders();
|
|
|
|
return err;
|
|
}
|
|
|
|
static MSG_SearchError msg_GetAttributesForScopes (
|
|
MSG_Master *master,
|
|
MSG_ScopeAttribute scope,
|
|
void **array, /* return available attribs in this scope */
|
|
uint16 numFolders, /* number of folders in the array */
|
|
MSG_SearchMenuItem *items, /* array of caller-allocated structs */
|
|
uint16 *maxItems, /* in- max array size; out- num returned */
|
|
XP_Bool forFilters)
|
|
{
|
|
if (NULL == items || NULL == maxItems || NULL == array)
|
|
return SearchError_NullPointer;
|
|
|
|
MSG_SearchError err = SearchError_Success;
|
|
int cnt = 0;
|
|
msg_SearchValidityTable *table = NULL;
|
|
|
|
DIR_Server *server = NULL;
|
|
MSG_FolderInfo **folderArray = NULL;
|
|
|
|
if (scope != scopeLdapDirectory)
|
|
{
|
|
folderArray = (MSG_FolderInfo**) array;
|
|
err = msg_GetValidityTableForFolders (master, folderArray, numFolders, &table, forFilters);
|
|
}
|
|
else
|
|
{
|
|
server = *(DIR_Server**) array;
|
|
XP_ASSERT(numFolders == 1);
|
|
if (numFolders > 1)
|
|
return SearchError_ScopeAgreement;
|
|
err = gValidityMgr.GetTable (msg_SearchValidityManager::Ldap, &table);
|
|
if (SearchError_Success == err)
|
|
gValidityMgr.PostProcessValidityTable (server);
|
|
}
|
|
|
|
if (SearchError_Success == err)
|
|
{
|
|
// For each attribute, look through its operator array until we find one which
|
|
// is available. Since only one available operator tells us that the attrib should
|
|
// be included in the list, we can stop there.
|
|
XP_Bool found;
|
|
int numArbHdrs = master->GetPrefs()->GetNumCustomHeaders();
|
|
uint16 numItems = table->GetNumAvailAttribs() + numArbHdrs;
|
|
|
|
if (numItems > *maxItems)
|
|
{
|
|
XP_ASSERT(0); // caller did not allocate enough menu items!!
|
|
numItems = *maxItems; // truncate # of options
|
|
}
|
|
|
|
uint16 arbHdrCnt = 0;
|
|
int idxAttrib = 0;
|
|
|
|
// loops while we have attributes we have not checked for and while we have
|
|
// space in the array of items to fit these new attributes....
|
|
while (idxAttrib < kNumAttributes && cnt < numItems)
|
|
{
|
|
found = FALSE;
|
|
for (int idxOp = 0; idxOp < kNumOperators && !found; idxOp++)
|
|
if (table->GetAvailable (idxAttrib, 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)
|
|
items[cnt].attrib = idxAttrib;
|
|
items[cnt].isEnabled = table->GetEnabled (idxAttrib, idxOp);
|
|
if (scope != scopeLdapDirectory)
|
|
{
|
|
char * name;
|
|
if (idxAttrib == attribOtherHeader && numArbHdrs) // do we have any arbitrary headers?
|
|
{
|
|
name = master->GetPrefs()->GetNthCustomHeader(arbHdrCnt);
|
|
arbHdrCnt++; // our offset for custom header prefs
|
|
numArbHdrs--; // we just processed a custom hdr
|
|
XP_STRNCPY_SAFE (items[cnt].name, name, sizeof(items[cnt].name));
|
|
XP_FREEIF(name); // free memory allocated by prefs...
|
|
cnt++;
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
else
|
|
if (idxAttrib != attribOtherHeader)
|
|
{
|
|
// ##mscott: hack...attribAgeInDays is not in the proper order for the resource file...
|
|
if (idxAttrib == attribAgeInDays) // not indexed properly in the resource file
|
|
name = XP_GetString(XP_SEARCH_AGE);
|
|
else
|
|
name = XP_GetString(idxAttrib + XP_SEARCH_SUBJECT);
|
|
XP_STRNCPY_SAFE (items[cnt].name, name, sizeof(items[cnt].name));
|
|
cnt++;
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DIR_AttributeId id;
|
|
if (SearchError_Success == MSG_SearchAttribToDirAttrib ((MSG_SearchAttribute) idxAttrib, &id))
|
|
{
|
|
const char *name = DIR_GetAttributeName (server, id);
|
|
XP_STRNCPY_SAFE (items[cnt].name, name, sizeof(items[cnt].name));
|
|
|
|
}
|
|
else
|
|
items[cnt].name[0] = '\0';
|
|
cnt++;
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
// don't increment idxAtrib if it is an attribOtherHeader and we still have a arbitrary hdrs that
|
|
// need processing. If we just looped through all the ops and did not find an available op for the
|
|
// attrib, then we can also increment to the next attibute
|
|
if (idxAttrib != attribOtherHeader || !numArbHdrs || !found) // don't increment if we still have more arbitrary hdrs to process
|
|
idxAttrib++;
|
|
}
|
|
}
|
|
*maxItems = cnt; // final number of attributes we added ot the items array
|
|
return err;
|
|
}
|
|
|
|
|
|
SEARCH_API XP_Bool MSG_ScopeUsesCustomHeaders(
|
|
MSG_Master * master,
|
|
MSG_ScopeAttribute scope,
|
|
void * selection, /* could be a folder or LDAP server. Determined by the scope */
|
|
XP_Bool forFilters) /* is this a filter we are talking about? */
|
|
{
|
|
MSG_FolderInfo * folder = NULL;
|
|
|
|
if (scope != scopeLdapDirectory)
|
|
{
|
|
folder = (MSG_FolderInfo *) selection; // safe cast because we used scope to determine it was a folderInfo
|
|
|
|
// determine if the user wants us to search locally or search the server by checking the pref...
|
|
// remember: this pref onlines applies for online imap and online news folders...(filters are left alone)
|
|
XP_Bool searchServer = master->GetPrefs()->GetSearchServer();
|
|
searchServer = forFilters ? TRUE : searchServer; // small hack to simplify the if statements...
|
|
if (folder)
|
|
{
|
|
if (folder->TestFlag(MSG_FOLDER_FLAG_NEWSGROUP) || folder->TestFlag(MSG_FOLDER_FLAG_NEWS_HOST))
|
|
{
|
|
// if we are searching the server && are not in offline mode, then we need to
|
|
// ask the server if it supports news extentions. If it does, we support custom headers,
|
|
// otherwise we don't.
|
|
if (searchServer && !NET_IsOffline() && !folder->KnowsSearchNntpExtension())
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE; // currently LDAP doesn't support custom headers. ##mscott
|
|
}
|
|
|
|
SEARCH_API XP_Bool MSG_IsStringAttribute(
|
|
MSG_SearchAttribute attrib)
|
|
{
|
|
return IsStringAttribute(attrib);
|
|
}
|
|
|
|
SEARCH_API MSG_SearchError MSG_GetNumAttributesForFilterScopes (
|
|
MSG_Master *master,
|
|
MSG_ScopeAttribute scope,
|
|
void ** array, /* return available attribs in this scope */
|
|
uint16 numFolders, /* number of folders in the array */
|
|
uint16 *numItems) /* out - number of attributes for the filter scope */
|
|
{
|
|
return msg_GetNumAttributesForScopes (master, scope, array, numFolders, numItems, TRUE);
|
|
}
|
|
|
|
SEARCH_API MSG_SearchError MSG_GetAttributesForFilterScopes (
|
|
MSG_Master *master,
|
|
MSG_ScopeAttribute scope,
|
|
void **array, /* return available attribs in this scope */
|
|
uint16 numFolders, /* number of folders in the array */
|
|
MSG_SearchMenuItem *items, /* array of caller-allocated structs */
|
|
uint16 *maxItems) /* in- max array size; out- num returned */
|
|
{
|
|
return msg_GetAttributesForScopes (master, scope, array, numFolders, items, maxItems, TRUE);
|
|
}
|
|
|
|
SEARCH_API MSG_SearchError MSG_GetNumAttributesForSearchScopes (
|
|
MSG_Master *master,
|
|
MSG_ScopeAttribute scope,
|
|
void ** array, /* return available attribs in this scope */
|
|
uint16 numFolders, /* number of folders in the array */
|
|
uint16 *numItems) /* out - number of attributes for the scope */
|
|
{
|
|
return msg_GetNumAttributesForScopes (master, scope, array, numFolders, numItems, FALSE);
|
|
}
|
|
|
|
SEARCH_API MSG_SearchError MSG_GetAttributesForSearchScopes (
|
|
MSG_Master *master,
|
|
MSG_ScopeAttribute scope,
|
|
void **array, /* return available attribs in this scope */
|
|
uint16 numFolders, /* number of folders in the array */
|
|
MSG_SearchMenuItem *items, /* array of caller-allocated structs */
|
|
uint16 *maxItems) /* in- max array size; out- num returned */
|
|
{
|
|
return msg_GetAttributesForScopes (master, scope, array, numFolders, items, maxItems, FALSE);
|
|
}
|
|
|
|
|
|
static MSG_SearchError msg_GetOperatorsForScopes (
|
|
MSG_Master *master,
|
|
MSG_ScopeAttribute scope,
|
|
void **array,
|
|
uint16 numFolders,
|
|
MSG_SearchAttribute attrib,
|
|
MSG_SearchMenuItem *items,
|
|
uint16 *maxItems,
|
|
XP_Bool forFilters)
|
|
{
|
|
if (NULL == items || NULL == maxItems || NULL == array)
|
|
return SearchError_NullPointer;
|
|
|
|
int cnt = 0;
|
|
msg_SearchValidityTable *table = NULL;
|
|
MSG_SearchError err = SearchError_Success;
|
|
|
|
MSG_FolderInfo **folderArray = NULL;
|
|
DIR_Server *server = NULL;
|
|
|
|
if (scope != scopeLdapDirectory)
|
|
{
|
|
folderArray = (MSG_FolderInfo **) array;
|
|
err = msg_GetValidityTableForFolders (master, folderArray, numFolders, &table, forFilters);
|
|
}
|
|
else
|
|
{
|
|
server = *(DIR_Server**) array;
|
|
err = gValidityMgr.GetTable (msg_SearchValidityManager::Ldap, &table);
|
|
}
|
|
|
|
// We know which attrib we're interested in, so we only need one 'for' loop
|
|
// to look through the operators which match. We need to know all of the
|
|
// matching operators, not just the first (as above)
|
|
|
|
if (SearchError_Success == err)
|
|
{
|
|
for (int idxOp = 0; idxOp < kNumOperators && cnt < *maxItems; idxOp++)
|
|
if (table->GetAvailable (attrib, idxOp))
|
|
{
|
|
// If we're talking about an LDAP server which can't do efficient
|
|
// wildcard matching, then don't show the operators which generate that syntax
|
|
if (scope == scopeLdapDirectory && (idxOp == opContains || idxOp == opDoesntContain) && !server->efficientWildcards)
|
|
continue;
|
|
|
|
char * name;
|
|
// ## mscott: hack because is greater than and is less than are not in the proper order in the resource file...
|
|
if (idxOp == opIsGreaterThan)
|
|
name = XP_GetString(XP_SEARCH_IS_GREATER);
|
|
else
|
|
if (idxOp == opIsLessThan)
|
|
name = XP_GetString(XP_SEARCH_IS_LESS);
|
|
else
|
|
name = XP_GetString(idxOp + XP_SEARCH_CONTAINS);
|
|
|
|
// This uses idxOp as an alias into MSG_SearchOperator
|
|
items[cnt].attrib = idxOp;
|
|
items[cnt].isEnabled = table->GetEnabled (attrib, idxOp);
|
|
// char *name = XP_GetString (idxOp + XP_SEARCH_CONTAINS);
|
|
XP_STRNCPY_SAFE (items[cnt].name, name, sizeof(items[cnt].name));
|
|
cnt++;
|
|
}
|
|
}
|
|
|
|
*maxItems = cnt;
|
|
return err;
|
|
}
|
|
|
|
|
|
SEARCH_API MSG_SearchError MSG_GetOperatorsForFilterScopes (
|
|
MSG_Master *master,
|
|
MSG_ScopeAttribute scope,
|
|
void **array,
|
|
uint16 numFolders,
|
|
MSG_SearchAttribute attrib,
|
|
MSG_SearchMenuItem *items,
|
|
uint16 *maxItems)
|
|
{
|
|
return msg_GetOperatorsForScopes (master, scope, array, numFolders, attrib, items, maxItems, TRUE);
|
|
}
|
|
|
|
|
|
SEARCH_API MSG_SearchError MSG_GetOperatorsForSearchScopes (
|
|
MSG_Master *master,
|
|
MSG_ScopeAttribute scope,
|
|
void **array,
|
|
uint16 numFolders,
|
|
MSG_SearchAttribute attrib,
|
|
MSG_SearchMenuItem *items,
|
|
uint16 *maxItems)
|
|
{
|
|
return msg_GetOperatorsForScopes (master, scope, array, numFolders, attrib, items, maxItems, FALSE);
|
|
}
|
|
|
|
|
|
static XP_Bool msg_FoldersAreHomogeneous (MSG_FolderInfo **folders, uint16 folderCount)
|
|
{
|
|
FolderType firstType=(FolderType)0;
|
|
XP_Bool firstIsRFC977bis = FALSE;
|
|
for (int i = 0; i < folderCount; i++)
|
|
{
|
|
FolderType type = folders[i]->GetType();
|
|
XP_Bool isRFC977bis = folders[i]->KnowsSearchNntpExtension();
|
|
if (i == 0)
|
|
{
|
|
firstType = type;
|
|
firstIsRFC977bis = isRFC977bis;
|
|
}
|
|
else
|
|
{
|
|
if (type != firstType)
|
|
return FALSE;
|
|
if (isRFC977bis != firstIsRFC977bis)
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
SEARCH_API MSG_SearchError MSG_GetScopeMenuForSearchMessages (
|
|
MSG_Master *master,
|
|
MSG_FolderInfo **selArray,
|
|
uint16 selCount,
|
|
MSG_SearchMenuItem *items,
|
|
uint16 *maxItems)
|
|
{
|
|
if (!master || !selArray || !items || !maxItems)
|
|
{
|
|
*maxItems = 0;
|
|
return SearchError_NullPointer;
|
|
}
|
|
|
|
if (*maxItems < 6)
|
|
{
|
|
*maxItems = 0;
|
|
return SearchError_ListTooSmall;
|
|
}
|
|
|
|
int idx = 0;
|
|
if (selCount == 1)
|
|
{
|
|
items[idx].attrib = -1; //###phil hacky way to tell the FE that "selected" is enabled
|
|
PR_snprintf (items[idx++].name, kSearchMenuLength, XP_GetString(MK_SEARCH_SCOPE_ONE), selArray[0]->GetPrettiestName());
|
|
}
|
|
else if (msg_FoldersAreHomogeneous (selArray, selCount))
|
|
{
|
|
items[idx].attrib = -1; //###phil hacky way to tell the FE that "selected" is enabled
|
|
XP_STRNCPY_SAFE (items[idx++].name, XP_GetString(MK_SEARCH_SCOPE_SELECTED), kSearchMenuLength );
|
|
}
|
|
|
|
if (master->GetPrefs()->GetMailServerIsIMAP4())
|
|
{
|
|
items[idx].attrib = scopeMailFolder;
|
|
XP_STRNCPY_SAFE (items[idx++].name, XP_GetString(MK_SEARCH_SCOPE_ONLINE_MAIL), kSearchMenuLength);
|
|
}
|
|
|
|
items[idx].attrib = scopeMailFolder;
|
|
XP_STRNCPY_SAFE (items[idx++].name, XP_GetString(MK_SEARCH_SCOPE_OFFLINE_MAIL), kSearchMenuLength);
|
|
|
|
items[idx].attrib = scopeNewsgroup;
|
|
XP_STRNCPY_SAFE (items[idx++].name, XP_GetString(MK_SEARCH_SCOPE_SUBSCRIBED_NEWS), kSearchMenuLength);
|
|
|
|
int i;
|
|
XP_Bool allAreRFC977bis = TRUE;
|
|
for (i = 0; i < selCount && allAreRFC977bis; i++)
|
|
allAreRFC977bis = selArray[i]->KnowsSearchNntpExtension();
|
|
if (allAreRFC977bis)
|
|
{
|
|
items[idx].attrib = scopeAllSearchableGroups;
|
|
XP_STRNCPY_SAFE (items[idx++].name, XP_GetString(MK_SEARCH_SCOPE_ALL_NEWS), kSearchMenuLength);
|
|
}
|
|
|
|
*maxItems = idx;
|
|
|
|
for (i = 0; i < idx; i++)
|
|
items[i].isEnabled = TRUE;
|
|
|
|
return SearchError_Success;
|
|
}
|
|
|
|
static MSG_SearchAttribute msg_DirAttribToSearchAttrib (DIR_AttributeId id)
|
|
{
|
|
switch (id)
|
|
{
|
|
case cn: return attribCommonName;
|
|
case mail : return attrib822Address;
|
|
case telephonenumber : return attribPhoneNumber;
|
|
case o: return attribOrganization;
|
|
case ou: return attribOrgUnit;
|
|
case l: return attribLocality;
|
|
case street : return attribStreetAddress;
|
|
case givenname :return attribGivenName;
|
|
case sn: return attribSurname;
|
|
case custom1: return attribCustom1;
|
|
case custom2: return attribCustom2;
|
|
case custom3: return attribCustom3;
|
|
case custom4: return attribCustom4;
|
|
case custom5: return attribCustom5;
|
|
default:
|
|
XP_ASSERT(FALSE);
|
|
}
|
|
return attribCommonName; // shouldn't get here
|
|
}
|
|
|
|
|
|
SEARCH_API MSG_SearchError MSG_GetBasicLdapSearchAttributes (DIR_Server *server, MSG_SearchMenuItem *items, int *maxItems)
|
|
{
|
|
XP_ASSERT(server && items && maxItems);
|
|
if (!server || !items || !maxItems)
|
|
return SearchError_NullPointer;
|
|
|
|
DIR_AttributeId defaults[4] = {cn, mail, o, ou};
|
|
MSG_SearchAttribute attrib;
|
|
const char *attribName = NULL;
|
|
|
|
for (int i = 0; i < *maxItems; i++)
|
|
{
|
|
if (i < server->basicSearchAttributesCount)
|
|
{
|
|
attrib = msg_DirAttribToSearchAttrib (server->basicSearchAttributes[i]);
|
|
attribName = DIR_GetAttributeName (server, server->basicSearchAttributes[i]);
|
|
}
|
|
else
|
|
{
|
|
attrib = msg_DirAttribToSearchAttrib (defaults[i]);
|
|
attribName = DIR_GetAttributeName (server, defaults[i]);
|
|
}
|
|
|
|
items[i].attrib = attrib;
|
|
items[i].isEnabled = TRUE; //probably pointless these days?
|
|
XP_ASSERT(attribName);
|
|
if (attribName)
|
|
XP_STRNCPY_SAFE (items[i].name, attribName, kSearchMenuLength);
|
|
}
|
|
|
|
return SearchError_Success;
|
|
}
|
|
|
|
|
|
SEARCH_API MSG_SearchError MSG_SearchAttribToDirAttrib (MSG_SearchAttribute attrib, DIR_AttributeId *id)
|
|
{
|
|
switch (attrib)
|
|
{
|
|
case attribCommonName: *id = cn; break;
|
|
case attrib822Address : *id = mail; break;
|
|
case attribPhoneNumber : *id = telephonenumber; break;
|
|
case attribOrganization: *id = o; break;
|
|
case attribOrgUnit: *id = ou; break;
|
|
case attribLocality: *id = l; break;
|
|
case attribStreetAddress : *id = street; break;
|
|
case attribGivenName : *id = givenname; break;
|
|
case attribSurname: *id = sn; break;
|
|
case attribCustom1: *id = custom1; break;
|
|
case attribCustom2: *id = custom2; break;
|
|
case attribCustom3: *id = custom3; break;
|
|
case attribCustom4: *id = custom4; break;
|
|
case attribCustom5: *id = custom5; break;
|
|
default:
|
|
return SearchError_ScopeAgreement;
|
|
}
|
|
return SearchError_Success;
|
|
}
|
|
|
|
SEARCH_API MSG_SearchError MSG_GetSearchWidgetForAttribute (
|
|
MSG_SearchAttribute attrib,
|
|
MSG_SearchValueWidget *widget)
|
|
{
|
|
if (NULL == widget)
|
|
return SearchError_NullPointer;
|
|
|
|
MSG_SearchError err = SearchError_Success;
|
|
|
|
switch (attrib)
|
|
{
|
|
case attribDate:
|
|
*widget = widgetDate;
|
|
break;
|
|
case attribAgeInDays:
|
|
*widget = widgetInt;
|
|
break;
|
|
case attribMsgStatus:
|
|
case attribPriority:
|
|
*widget = widgetMenu;
|
|
break;
|
|
default:
|
|
if (attrib < kNumAttributes)
|
|
if (attrib != attribLocation)
|
|
*widget = widgetText;
|
|
else
|
|
err = SearchError_ScopeAgreement;
|
|
else
|
|
err = SearchError_InvalidAttribute;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
SEARCH_API MSG_PRIORITY MSG_GetPriorityFromString(const char *priority)
|
|
{
|
|
if (strcasestr(priority, "Normal") != NULL)
|
|
return MSG_NormalPriority;
|
|
else if (strcasestr(priority, "Lowest") != NULL)
|
|
return MSG_LowestPriority;
|
|
else if (strcasestr(priority, "Highest") != NULL)
|
|
return MSG_HighestPriority;
|
|
else if (strcasestr(priority, "High") != NULL ||
|
|
strcasestr(priority, "Urgent") != NULL)
|
|
return MSG_HighPriority;
|
|
else if (strcasestr(priority, "Low") != NULL ||
|
|
strcasestr(priority, "Non-urgent") != NULL)
|
|
return MSG_LowPriority;
|
|
else if (strcasestr(priority, "1") != NULL)
|
|
return MSG_HighestPriority;
|
|
else if (strcasestr(priority, "2") != NULL)
|
|
return MSG_HighPriority;
|
|
else if (strcasestr(priority, "3") != NULL)
|
|
return MSG_NormalPriority;
|
|
else if (strcasestr(priority, "4") != NULL)
|
|
return MSG_LowPriority;
|
|
else if (strcasestr(priority, "5") != NULL)
|
|
return MSG_LowestPriority;
|
|
else
|
|
return MSG_NormalPriority;
|
|
//return MSG_NoPriority;
|
|
}
|
|
|
|
|
|
SEARCH_API void MSG_GetUntranslatedPriorityName (MSG_PRIORITY p, char *outName, uint16 maxOutName)
|
|
{
|
|
char *tmpOutName = NULL;
|
|
switch (p)
|
|
{
|
|
case MSG_PriorityNotSet:
|
|
case MSG_NoPriority:
|
|
tmpOutName = "None";
|
|
break;
|
|
case MSG_LowestPriority:
|
|
tmpOutName = "Lowest";
|
|
break;
|
|
case MSG_LowPriority:
|
|
tmpOutName = "Low";
|
|
break;
|
|
case MSG_NormalPriority:
|
|
tmpOutName = "Normal";
|
|
break;
|
|
case MSG_HighPriority:
|
|
tmpOutName = "High";
|
|
break;
|
|
case MSG_HighestPriority:
|
|
tmpOutName = "Highest";
|
|
break;
|
|
default:
|
|
XP_ASSERT(FALSE);
|
|
}
|
|
|
|
if (tmpOutName)
|
|
XP_STRNCPY_SAFE (outName, tmpOutName, maxOutName);
|
|
else
|
|
outName[0] = '\0';
|
|
}
|
|
|
|
|
|
SEARCH_API void MSG_GetPriorityName (MSG_PRIORITY p, char *outName, uint16 maxOutName)
|
|
{
|
|
char *tmpOutName = NULL;
|
|
switch (p)
|
|
{
|
|
case MSG_PriorityNotSet:
|
|
case MSG_NoPriority:
|
|
tmpOutName = XP_GetString(XP_PRIORITY_NONE);
|
|
break;
|
|
case MSG_LowestPriority:
|
|
tmpOutName = XP_GetString(XP_PRIORITY_LOWEST);
|
|
break;
|
|
case MSG_LowPriority:
|
|
tmpOutName = XP_GetString(XP_PRIORITY_LOW);
|
|
break;
|
|
case MSG_NormalPriority:
|
|
tmpOutName = XP_GetString(XP_PRIORITY_NORMAL);
|
|
break;
|
|
case MSG_HighPriority:
|
|
tmpOutName = XP_GetString(XP_PRIORITY_HIGH);
|
|
break;
|
|
case MSG_HighestPriority:
|
|
tmpOutName = XP_GetString(XP_PRIORITY_HIGHEST);
|
|
break;
|
|
default:
|
|
XP_ASSERT(FALSE);
|
|
}
|
|
|
|
if (tmpOutName)
|
|
XP_STRNCPY_SAFE (outName, tmpOutName, maxOutName);
|
|
else
|
|
outName[0] = '\0';
|
|
}
|
|
|
|
|
|
SEARCH_API void MSG_GetStatusName (uint32 s, char *outName, uint16 maxOutName)
|
|
{
|
|
char *tmpOutName = NULL;
|
|
#define MSG_STATUS_MASK (MSG_FLAG_READ | MSG_FLAG_REPLIED | MSG_FLAG_FORWARDED | MSG_FLAG_NEW)
|
|
uint32 maskOut = (s & MSG_STATUS_MASK);
|
|
|
|
// diddle the flags to pay attention to the most important ones first, if multiple
|
|
// flags are set. Should remove this code from the winfe.
|
|
if (maskOut & MSG_FLAG_NEW)
|
|
maskOut = MSG_FLAG_NEW;
|
|
if ( maskOut & MSG_FLAG_REPLIED &&
|
|
maskOut & MSG_FLAG_FORWARDED )
|
|
maskOut = MSG_FLAG_REPLIED|MSG_FLAG_FORWARDED;
|
|
else if ( maskOut & MSG_FLAG_FORWARDED )
|
|
maskOut = MSG_FLAG_FORWARDED;
|
|
else if ( maskOut & MSG_FLAG_REPLIED )
|
|
maskOut = MSG_FLAG_REPLIED;
|
|
|
|
switch (maskOut)
|
|
{
|
|
case MSG_FLAG_READ:
|
|
tmpOutName = XP_GetString(XP_STATUS_READ);
|
|
break;
|
|
case MSG_FLAG_REPLIED:
|
|
tmpOutName = XP_GetString(XP_STATUS_REPLIED);
|
|
break;
|
|
case MSG_FLAG_FORWARDED:
|
|
tmpOutName = XP_GetString(XP_STATUS_FORWARDED);
|
|
break;
|
|
case MSG_FLAG_FORWARDED|MSG_FLAG_REPLIED:
|
|
tmpOutName = XP_GetString(XP_STATUS_REPLIED_AND_FORWARDED);
|
|
break;
|
|
case MSG_FLAG_NEW:
|
|
tmpOutName = XP_GetString(XP_STATUS_NEW);
|
|
break;
|
|
default:
|
|
// This is fine, status may be "unread" for example
|
|
break;
|
|
}
|
|
|
|
if (tmpOutName)
|
|
XP_STRNCPY_SAFE (outName, tmpOutName, maxOutName);
|
|
else
|
|
outName[0] = '\0';
|
|
}
|
|
|
|
SEARCH_API uint32 MSG_GetStatusValueFromName(char *name)
|
|
{
|
|
char *tmpOutName;
|
|
|
|
tmpOutName = XP_GetString(XP_STATUS_READ);
|
|
if (tmpOutName && !XP_STRCMP(tmpOutName, name))
|
|
return MSG_FLAG_READ;
|
|
tmpOutName = XP_GetString(XP_STATUS_REPLIED);
|
|
if (tmpOutName && !XP_STRCMP(tmpOutName, name))
|
|
return MSG_FLAG_REPLIED;
|
|
tmpOutName = XP_GetString(XP_STATUS_FORWARDED);
|
|
if (tmpOutName && !XP_STRCMP(tmpOutName, name))
|
|
return MSG_FLAG_FORWARDED;
|
|
tmpOutName = XP_GetString(XP_STATUS_REPLIED_AND_FORWARDED);
|
|
if (tmpOutName && !XP_STRCMP(tmpOutName, name))
|
|
return MSG_FLAG_FORWARDED|MSG_FLAG_REPLIED;
|
|
tmpOutName = XP_GetString(XP_STATUS_NEW);
|
|
if (tmpOutName && !XP_STRCMP(tmpOutName, name))
|
|
return MSG_FLAG_NEW;
|
|
return 0;
|
|
}
|
|
|
|
SEARCH_API MSG_SearchError MSG_GetLdapObjectClasses (MSG_Pane *pane,
|
|
char **objectClassList, uint16 *maxItems)
|
|
{
|
|
XP_ASSERT(objectClassList);
|
|
XP_ASSERT(maxItems);
|
|
|
|
MSG_SearchFrame *frame = MSG_SearchFrame::FromPane (pane);
|
|
if (!frame)
|
|
return SearchError_InvalidPane;
|
|
return frame->GetLdapObjectClasses (objectClassList, maxItems);
|
|
}
|
|
|
|
|
|
SEARCH_API MSG_SearchError MSG_AddAllSearchableGroupsStatus (MSG_Pane * /*searchPane*/, XP_Bool * /*enabled*/)
|
|
{
|
|
return SearchError_NotImplemented;
|
|
}
|
|
|
|
|
|
SEARCH_API MSG_SearchError MSG_AddAllSearchableGroups (MSG_Pane * /*searchPane*/)
|
|
{
|
|
return SearchError_NotImplemented;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//--------------------- These are called from libnet --------------------------
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
// Since each search can run over multiple scopes, and scopes of different
|
|
// types (e.g. online mail and offline mail), we use one meta-URL of the
|
|
// format "search:", which watches to see when each scope finishes, and
|
|
// then fires off the next one in serial.
|
|
//
|
|
SEARCH_API int MSG_ProcessSearch (MWContext *context)
|
|
{
|
|
MSG_SearchFrame *frame = MSG_SearchFrame::FromContext (context);
|
|
XP_ASSERT(frame);
|
|
|
|
return frame->TimeSlice();
|
|
}
|
|
|
|
|
|
SEARCH_API int MSG_InterruptSearch (MWContext *context)
|
|
{
|
|
MSG_SearchFrame *frame = MSG_SearchFrame::FromContext (context);
|
|
XP_ASSERT(frame);
|
|
|
|
if (frame)
|
|
{
|
|
msg_SearchAdapter *adapter = frame->GetRunningAdapter();
|
|
if (adapter)
|
|
return frame->GetRunningAdapter()->Abort();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
SEARCH_API MSG_SearchTermArray *MSG_CreateSearchTermArray()
|
|
{
|
|
return new MSG_SearchTermArray;
|
|
}
|
|
|
|
SEARCH_API MSG_SearchError MSG_SearchGetNumTerms(MSG_SearchTermArray *terms, int32 *numTerms)
|
|
{
|
|
if (!terms || !numTerms)
|
|
return SearchError_NullPointer;
|
|
*numTerms = terms->GetSize();
|
|
return SearchError_Success;
|
|
}
|
|
|
|
SEARCH_API MSG_SearchError MSG_AddSearchTermToArray(
|
|
MSG_SearchTermArray *array,
|
|
MSG_SearchAttribute attrib, /* attribute for this term */
|
|
MSG_SearchOperator op, /* operator e.g. opContains */
|
|
MSG_SearchValue *value, /* value e.g. "Fred" */
|
|
XP_Bool BooleanAND, /* boolean AND operator? */
|
|
char * arbitraryHeader) /* user defined arbitrary header. ignored unless attrib = attribOtherHeader */
|
|
{
|
|
MSG_SearchTerm *newSearchTerm = new MSG_SearchTerm(attrib, op, value, BooleanAND, arbitraryHeader);
|
|
if (newSearchTerm == NULL)
|
|
return SearchError_OutOfMemory;
|
|
array->Add(newSearchTerm);
|
|
return SearchError_Success;
|
|
}
|
|
|
|
SEARCH_API MSG_SearchError MSG_SearchGetTerm(MSG_SearchTermArray *array, int32 termIndex,
|
|
MSG_SearchAttribute *attrib, /* attribute for this term */
|
|
MSG_SearchOperator *op, /* operator e.g. opContains */
|
|
MSG_SearchValue *value) /* value e.g. "Fred" */
|
|
{
|
|
MSG_SearchTerm *term = (MSG_SearchTerm *) array->GetAt(termIndex);
|
|
if (term == NULL)
|
|
return SearchError_InvalidIndex;
|
|
*attrib = term->m_attribute;
|
|
*op = term->m_operator;
|
|
*value = term->m_value;
|
|
return SearchError_Success;
|
|
}
|
|
|
|
|
|
SEARCH_API void MSG_DestroySearchTermArray(MSG_SearchTermArray *array)
|
|
{
|
|
delete array;
|
|
}
|
|
|
|
|
|
SEARCH_API char *MSG_EscapeSearchUrl (const char *nntpCommand)
|
|
{
|
|
char *result = NULL;
|
|
// max escaped length is two extra characters for every character in the cmd.
|
|
char *scratchBuf = (char*) XP_ALLOC (3*XP_STRLEN(nntpCommand) + 1);
|
|
if (scratchBuf)
|
|
{
|
|
char *scratchPtr = scratchBuf;
|
|
while (1)
|
|
{
|
|
char ch = *nntpCommand++;
|
|
if (!ch)
|
|
break;
|
|
if (ch == '#' || ch == '?' || ch == '@' || ch == '\\')
|
|
{
|
|
*scratchPtr++ = '\\';
|
|
sprintf (scratchPtr, "%X", ch);
|
|
scratchPtr += 2;
|
|
}
|
|
else
|
|
*scratchPtr++ = ch;
|
|
}
|
|
*scratchPtr = '\0';
|
|
result = XP_STRDUP (scratchBuf); // realloc down to smaller size
|
|
XP_FREE (scratchBuf);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
static
|
|
char *msg_EscapeImapSearchProtocol(const char *imapCommand)
|
|
{
|
|
char *result = NULL;
|
|
// max escaped length is one extra character for every character in the cmd.
|
|
char *scratchBuf = (char*) XP_ALLOC (2*XP_STRLEN(imapCommand) + 1);
|
|
if (scratchBuf)
|
|
{
|
|
char *scratchPtr = scratchBuf;
|
|
while (1)
|
|
{
|
|
char ch = *imapCommand++;
|
|
if (!ch)
|
|
break;
|
|
if (ch == '\\')
|
|
{
|
|
*scratchPtr++ = '\\';
|
|
*scratchPtr++ = '\\';
|
|
}
|
|
else
|
|
*scratchPtr++ = ch;
|
|
}
|
|
*scratchPtr = '\0';
|
|
result = XP_STRDUP (scratchBuf); // realloc down to smaller size
|
|
XP_FREE (scratchBuf);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
SEARCH_API char *MSG_UnEscapeSearchUrl (const char *commandSpecificData)
|
|
{
|
|
char *result = (char*) XP_ALLOC (XP_STRLEN(commandSpecificData) + 1);
|
|
if (result)
|
|
{
|
|
char *resultPtr = result;
|
|
while (1)
|
|
{
|
|
char ch = *commandSpecificData++;
|
|
if (!ch)
|
|
break;
|
|
if (ch == '\\')
|
|
{
|
|
char scratchBuf[3];
|
|
scratchBuf[0] = (char) *commandSpecificData++;
|
|
scratchBuf[1] = (char) *commandSpecificData++;
|
|
scratchBuf[2] = '\0';
|
|
int accum = 0;
|
|
sscanf (scratchBuf, "%X", &accum);
|
|
*resultPtr++ = (char) accum;
|
|
}
|
|
else
|
|
*resultPtr++ = ch;
|
|
}
|
|
*resultPtr = '\0';
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
SEARCH_API XP_Bool MSG_AcquireEditHeadersSemaphore (MSG_Master * master, void * holder)
|
|
{
|
|
if (master)
|
|
return master->AcquireEditHeadersSemaphore(holder);
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
SEARCH_API XP_Bool MSG_ReleaseEditHeadersSemaphore (MSG_Master * master, void * holder)
|
|
{
|
|
if (master)
|
|
return master->ReleaseEditHeadersSemaphore(holder);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//------------------- Implementation of public opaque types -------------------
|
|
//-----------------------------------------------------------------------------
|
|
|
|
uint32 MSG_ResultElement::m_expectedMagic = 0x73726573; // 'sres'
|
|
uint32 MSG_SearchTerm::m_expectedMagic = 0x7374726d; // 'strm'
|
|
uint32 MSG_ScopeTerm::m_expectedMagic = 0x7373636f; // 'ssco'
|
|
uint32 MSG_SearchFrame::m_expectedMagic = 0x7366726d; // 'sfrm'
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// MSG_ResultElement implementation
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
MSG_ResultElement::MSG_ResultElement(msg_SearchAdapter *adapter) : msg_OpaqueObject (m_expectedMagic)
|
|
{
|
|
m_adapter = adapter;
|
|
}
|
|
|
|
|
|
MSG_ResultElement::~MSG_ResultElement ()
|
|
{
|
|
for (int i = 0; i < m_valueList.GetSize(); i++)
|
|
{
|
|
MSG_SearchValue *value = m_valueList.GetAt(i);
|
|
if (value->attribute == attribJpegFile)
|
|
{
|
|
char *url = XP_PlatformFileToURL (value->u.string);
|
|
char *tmp = url + XP_STRLEN("file://");
|
|
XP_FileRemove (tmp, xpMailFolder /*###phil hacky*/);
|
|
XP_FREE(url);
|
|
}
|
|
MSG_ResultElement::DestroyValue (value);
|
|
}
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_ResultElement::AddValue (MSG_SearchValue *value)
|
|
{
|
|
m_valueList.Add (value);
|
|
return SearchError_Success;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_ResultElement::DestroyValue (MSG_SearchValue *value)
|
|
{
|
|
if (IsStringAttribute(value->attribute))
|
|
{
|
|
XP_ASSERT(value->u.string);
|
|
XP_FREE (value->u.string);
|
|
}
|
|
delete value;
|
|
return SearchError_Success;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_ResultElement::AssignValues (MSG_SearchValue *src, MSG_SearchValue *dst)
|
|
{
|
|
// Yes, this could be an operator overload, but MSG_SearchValue is totally public, so I'd
|
|
// have to define a derived class with nothing by operator=, and that seems like a bit much
|
|
MSG_SearchError err = SearchError_Success;
|
|
switch (src->attribute)
|
|
{
|
|
case attribPriority:
|
|
dst->attribute = src->attribute;
|
|
dst->u.priority = src->u.priority;
|
|
break;
|
|
case attribDate:
|
|
dst->attribute = src->attribute;
|
|
dst->u.date = src->u.date;
|
|
break;
|
|
case attribMsgStatus:
|
|
dst->attribute = src->attribute;
|
|
dst->u.msgStatus = src->u.msgStatus;
|
|
break;
|
|
case attribMessageKey:
|
|
dst->attribute = src->attribute;
|
|
dst->u.key = src->u.key;
|
|
break;
|
|
case attribAgeInDays:
|
|
dst->attribute = src->attribute;
|
|
// hack, mscott...setting age to 1 by default until the FEs implement Age by day...
|
|
// this will of course break the purge feature right now. WILL REPLACE ONCE FEs implement age by Day
|
|
// ##mscott WINFE has checked in age in days support...
|
|
#if defined(XP_WIN)
|
|
dst->u.age = src->u.age;
|
|
#else
|
|
dst->u.age = 1; // only temporary!
|
|
#endif
|
|
|
|
break;
|
|
default:
|
|
if (src->attribute < kNumAttributes)
|
|
{
|
|
XP_ASSERT(IsStringAttribute(src->attribute));
|
|
dst->attribute = src->attribute;
|
|
dst->u.string = XP_STRDUP(src->u.string);
|
|
if (!dst->u.string)
|
|
err = SearchError_OutOfMemory;
|
|
}
|
|
else
|
|
err = SearchError_InvalidAttribute;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_ResultElement::GetValue (MSG_SearchAttribute attrib, MSG_SearchValue **outValue) const
|
|
{
|
|
MSG_SearchError err = SearchError_ScopeAgreement;
|
|
MSG_SearchValue *value = NULL;
|
|
*outValue = NULL;
|
|
|
|
for (int i = 0; i < m_valueList.GetSize() && err != SearchError_Success; i++)
|
|
{
|
|
value = m_valueList.GetAt(i);
|
|
if (attrib == value->attribute)
|
|
{
|
|
*outValue = new MSG_SearchValue;
|
|
if (*outValue)
|
|
{
|
|
err = AssignValues (value, *outValue);
|
|
err = SearchError_Success;
|
|
}
|
|
else
|
|
err = SearchError_OutOfMemory;
|
|
}
|
|
}
|
|
|
|
// No need to store the folderInfo separately; we can always get it if/when
|
|
// we need it. This code is to support "view thread context" in the search dialog
|
|
if (SearchError_ScopeAgreement == err && attrib == attribFolderInfo)
|
|
{
|
|
MSG_FolderInfo *targetFolder = m_adapter->FindTargetFolder (this);
|
|
if (targetFolder)
|
|
{
|
|
*outValue = new MSG_SearchValue;
|
|
if (*outValue)
|
|
{
|
|
(*outValue)->u.folder = targetFolder;
|
|
(*outValue)->attribute = attribFolderInfo;
|
|
err = SearchError_Success;
|
|
}
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
const MSG_SearchValue *MSG_ResultElement::GetValueRef (MSG_SearchAttribute attrib) const
|
|
{
|
|
MSG_SearchValue *value = NULL;
|
|
for (int i = 0; i < m_valueList.GetSize(); i++)
|
|
{
|
|
value = m_valueList.GetAt(i);
|
|
if (attrib == value->attribute)
|
|
return value;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_ResultElement::GetPrettyName (MSG_SearchValue **value)
|
|
{
|
|
MSG_SearchError err = GetValue (attribLocation, value);
|
|
if (SearchError_Success == err)
|
|
{
|
|
MSG_FolderInfo *folder = m_adapter->m_scope->m_folder;
|
|
MSG_NewsHost *host = NULL;
|
|
if (folder)
|
|
{
|
|
// Find the news host because only the host knows whether pretty
|
|
// names are supported.
|
|
if (FOLDER_CONTAINERONLY == folder->GetType())
|
|
host = ((MSG_NewsFolderInfoContainer*) folder)->GetHost();
|
|
else if (folder->IsNews())
|
|
host = folder->GetNewsFolderInfo()->GetHost();
|
|
|
|
// Ask the host whether it knows pretty names. It isn't strictly
|
|
// necessary to avoid calling folder->GetPrettiestName() since it
|
|
// does the right thing. But we do have to find the folder from the host.
|
|
if (host && host->QueryExtension ("LISTPNAMES"))
|
|
{
|
|
folder = host->FindGroup ((*value)->u.string);
|
|
if (folder)
|
|
{
|
|
char *tmp = XP_STRDUP (folder->GetPrettiestName());
|
|
if (tmp)
|
|
{
|
|
XP_FREE ((*value)->u.string);
|
|
(*value)->u.string = tmp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int MSG_ResultElement::CompareByFolderInfoPtrs (const void *e1, const void *e2)
|
|
{
|
|
MSG_ResultElement * re1 = *(MSG_ResultElement **) e1;
|
|
MSG_ResultElement * re2 = *(MSG_ResultElement **) e2;
|
|
|
|
// get the src folder for each one
|
|
|
|
const MSG_SearchValue * v1 = re1->GetValueRef(attribFolderInfo);
|
|
const MSG_SearchValue * v2 = re2->GetValueRef(attribFolderInfo);
|
|
|
|
if (!v1 || !v2)
|
|
return 0;
|
|
|
|
return (v1->u.folder - v2->u.folder);
|
|
}
|
|
|
|
|
|
|
|
int MSG_ResultElement::Compare (const void *e1, const void *e2)
|
|
{
|
|
// Bad idea to cast away const, but they're my objects anway.
|
|
// Maybe if we go through and const everything this should be a const ptr.
|
|
MSG_ResultElement *re1 = *(MSG_ResultElement**) e1;
|
|
MSG_ResultElement *re2 = *(MSG_ResultElement**) e2;
|
|
|
|
XP_ASSERT(re1->IsValid());
|
|
XP_ASSERT(re2->IsValid());
|
|
|
|
MSG_SearchAttribute attrib = re1->m_adapter->m_scope->m_frame->m_sortAttribute;
|
|
|
|
const MSG_SearchValue *v1 = re1->GetValueRef (attrib);
|
|
const MSG_SearchValue *v2 = re2->GetValueRef (attrib);
|
|
|
|
int ret = 0;
|
|
if (!v1 || !v2)
|
|
return ret; // search result doesn't contain the attrib we want to sort on
|
|
|
|
switch (attrib)
|
|
{
|
|
case attribDate:
|
|
{
|
|
// on Win 3.1, the requisite 'int' return type is a short, so use a
|
|
// real time_t for comparison
|
|
time_t date = v1->u.date - v2->u.date;
|
|
if (date)
|
|
ret = ((long)date) < 0 ? -1 : 1;
|
|
else
|
|
ret = 0;
|
|
}
|
|
break;
|
|
case attribPriority:
|
|
ret = v1->u.priority - v2->u.priority;
|
|
break;
|
|
case attribMsgStatus:
|
|
{
|
|
// Here's an arbitrary sorting protocol for msg status
|
|
uint32 s1, s2;
|
|
|
|
s1 = v1->u.msgStatus & ~MSG_FLAG_REPLIED;
|
|
s2 = v2->u.msgStatus & ~MSG_FLAG_REPLIED;
|
|
if (s1 || s2)
|
|
ret = s1 - s2;
|
|
else
|
|
{
|
|
s1 = v1->u.msgStatus & ~MSG_FLAG_FORWARDED;
|
|
s2 = v2->u.msgStatus & ~MSG_FLAG_FORWARDED;
|
|
if (s1 || s2)
|
|
ret = s1 - s2;
|
|
else
|
|
{
|
|
s1 = v1->u.msgStatus & ~MSG_FLAG_READ;
|
|
s2 = v2->u.msgStatus & ~MSG_FLAG_READ;
|
|
if (s1 || s2)
|
|
ret = s1 - s2;
|
|
else
|
|
// statuses don't contain any flags we're interested in,
|
|
// so they're equal as far as we care
|
|
ret = 0;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
if (attrib == attribSubject)
|
|
{
|
|
// Special case for subjects, so "Re:foo" sorts under 'f' not 'r'
|
|
const char *s1 = v1->u.string;
|
|
const char *s2 = v2->u.string;
|
|
msg_StripRE (&s1, NULL);
|
|
msg_StripRE (&s2, NULL);
|
|
ret = strcasecomp (s1, s2);
|
|
}
|
|
else
|
|
ret = strcasecomp (v1->u.string, v2->u.string);
|
|
}
|
|
|
|
// qsort's default sort order is ascending, so in order to get descending
|
|
// behavior, we'll tell qsort a lie and reverse the comparison order.
|
|
if (re1->m_adapter->m_scope->m_frame->m_descending && ret != 0)
|
|
if (ret < 0)
|
|
ret = 1;
|
|
else
|
|
ret = -1;
|
|
|
|
// <0 --> e1 less than e2
|
|
// 0 --> e1 equal to e2
|
|
// >0 --> e1 greater than e2
|
|
return ret;
|
|
}
|
|
|
|
|
|
MWContextType MSG_ResultElement::GetContextType ()
|
|
{
|
|
MWContextType type=(MWContextType)0;
|
|
switch (m_adapter->m_scope->m_attribute)
|
|
{
|
|
case scopeMailFolder:
|
|
type = MWContextMailMsg;
|
|
break;
|
|
case scopeOfflineNewsgroup: // added by mscott could be bug fix...
|
|
case scopeNewsgroup:
|
|
case scopeAllSearchableGroups:
|
|
type = MWContextNewsMsg;
|
|
break;
|
|
case scopeLdapDirectory:
|
|
type = MWContextBrowser;
|
|
break;
|
|
default:
|
|
XP_ASSERT(FALSE); // should never happen
|
|
}
|
|
return type;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_ResultElement::Open (void *window)
|
|
{
|
|
MSG_MessagePane *msgPane = NULL;
|
|
MWContext *context = NULL;
|
|
|
|
// ###phil this is a little ugly, but I'll wait before spending more time on it
|
|
// until the libnet rework is done and I know what kind of context we'll end up with
|
|
|
|
if (window)
|
|
{
|
|
if (m_adapter->m_scope->m_attribute != scopeLdapDirectory)
|
|
{
|
|
msgPane = (MSG_MessagePane *) window;
|
|
XP_ASSERT (MSG_MESSAGEPANE == msgPane->GetPaneType());
|
|
return m_adapter->OpenResultElement (msgPane, this);
|
|
}
|
|
else
|
|
{
|
|
context = (MWContext*) window;
|
|
XP_ASSERT (MWContextBrowser == context->type);
|
|
msg_SearchLdap *thisAdapter = (msg_SearchLdap*) m_adapter;
|
|
return thisAdapter->OpenResultElement (context, this);
|
|
}
|
|
}
|
|
return SearchError_NullPointer;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//----------------------- Base class for search objects -----------------------
|
|
//-----------------------------------------------------------------------------
|
|
|
|
msg_SearchAdapter::msg_SearchAdapter (MSG_ScopeTerm *scope, MSG_SearchTermArray &termList) : m_searchTerms(termList)
|
|
{
|
|
m_abortCalled = FALSE;
|
|
m_scope = scope;
|
|
}
|
|
|
|
|
|
msg_SearchAdapter::~msg_SearchAdapter ()
|
|
{
|
|
}
|
|
|
|
|
|
MSG_SearchError msg_SearchAdapter::ValidateTerms ()
|
|
{
|
|
if (!m_scope->IsValid())
|
|
return SearchError_InvalidScopeTerm;
|
|
|
|
MSG_SearchTerm *searchTerm = NULL;
|
|
for (int i = 0; i < m_searchTerms.GetSize(); i++)
|
|
{
|
|
searchTerm = m_searchTerms.GetAt(i);
|
|
if (!searchTerm->IsValid())
|
|
return SearchError_InvalidSearchTerm;
|
|
}
|
|
|
|
return SearchError_Success;
|
|
}
|
|
|
|
|
|
int msg_SearchAdapter::Abort ()
|
|
{
|
|
// Don't leave progress artifacts in the search dialog
|
|
FE_SetProgressBarPercent (m_scope->m_frame->GetContext(), 0);
|
|
|
|
m_abortCalled = TRUE;
|
|
return 0;
|
|
}
|
|
|
|
|
|
MSG_SearchError msg_SearchAdapter::OpenNewsResultInUnknownGroup (MSG_MessagePane *pane, MSG_ResultElement *result)
|
|
{
|
|
// Here's a hacky little way to open a news article given only the host and message-id
|
|
//
|
|
// A better fix might be to open the newsgroup (via MessagePane::LoadFolder)
|
|
// and then chain a LoadMessage after that.
|
|
|
|
MSG_SearchError err = SearchError_Unknown;
|
|
|
|
MSG_NewsHost *host = NULL;
|
|
MSG_FolderInfoNews *newsFolder = m_scope->m_folder->GetNewsFolderInfo();
|
|
if (newsFolder)
|
|
host = newsFolder->GetHost();
|
|
else if (FOLDER_CONTAINERONLY == m_scope->m_folder->GetType())
|
|
host = ((MSG_NewsFolderInfoContainer*) m_scope->m_folder)->GetHost();
|
|
|
|
if (host)
|
|
{
|
|
const MSG_SearchValue *messageIdValue = result->GetValueRef (attribMessageId);
|
|
if (messageIdValue)
|
|
{
|
|
char *url = PR_smprintf ("%s/%s", host->GetURLBase(), messageIdValue->u.string);
|
|
if (url)
|
|
{
|
|
URL_Struct *urlStruct = NET_CreateURLStruct (url, NET_DONT_RELOAD);
|
|
if (urlStruct)
|
|
{
|
|
urlStruct->internal_url = TRUE;
|
|
if (0 == pane->GetURL (urlStruct, TRUE))
|
|
err = SearchError_Success;
|
|
}
|
|
else
|
|
err = SearchError_OutOfMemory;
|
|
XP_FREE(url);
|
|
}
|
|
else
|
|
err = SearchError_OutOfMemory;
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
MSG_SearchError msg_SearchAdapter::OpenResultElement (MSG_MessagePane *pane, MSG_ResultElement *result)
|
|
{
|
|
MSG_SearchError err = SearchError_DBOpenFailed; // not true, really
|
|
INTL_CharSetInfo c = LO_GetDocumentCharacterSetInfo(pane->GetContext());
|
|
|
|
const MSG_SearchValue *keyValue = result->GetValueRef (attribMessageKey);
|
|
if (keyValue)
|
|
{
|
|
MSG_FolderInfo *targetFolder = FindTargetFolder (result);
|
|
|
|
if (targetFolder)
|
|
{
|
|
int16 csid = INTL_GetCSIWinCSID(c);
|
|
if (!csid)
|
|
{
|
|
csid = targetFolder->GetFolderCSID();
|
|
if (!csid)
|
|
csid = INTL_DefaultWinCharSetID(0);
|
|
INTL_SetCSIWinCSID(c, csid);
|
|
}
|
|
|
|
if (0 == pane->LoadMessage (targetFolder, keyValue->u.key, NULL, TRUE))
|
|
err = SearchError_Success;
|
|
}
|
|
else
|
|
err = OpenNewsResultInUnknownGroup (pane, result);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
MSG_FolderInfo *msg_SearchAdapter::FindTargetFolder (const MSG_ResultElement *result)
|
|
{
|
|
MSG_FolderInfo *targetFolder = m_scope->m_folder;
|
|
if (!targetFolder)
|
|
return NULL;
|
|
|
|
if (m_scope->m_attribute == scopeAllSearchableGroups)
|
|
{
|
|
const MSG_SearchValue *locValue = result->GetValueRef (attribLocation);
|
|
if (locValue)
|
|
{
|
|
XP_ASSERT(m_scope->m_folder->GetType() == FOLDER_CONTAINERONLY);
|
|
MSG_NewsHost *host = ((MSG_NewsFolderInfoContainer*) m_scope->m_folder)->GetHost();
|
|
|
|
if (host)
|
|
targetFolder = host->FindGroup (locValue->u.string);
|
|
}
|
|
}
|
|
return targetFolder;
|
|
}
|
|
|
|
|
|
MSG_SearchError msg_SearchAdapter::ModifyResultElement (MSG_ResultElement*, MSG_SearchValue*)
|
|
{
|
|
// The derived classes should override if they want to allow modification.
|
|
// So if we got to the base class, we don't allow it.
|
|
return SearchError_ScopeAgreement;
|
|
}
|
|
|
|
// This stuff lives in the base class because the IMAP search syntax
|
|
// is used by the RFC977bis SEARCH command as well as IMAP itself
|
|
|
|
// km - the NOT and HEADER strings are not encoded with a trailing
|
|
// <space> because they always precede a mnemonic that has a
|
|
// preceding <space> and double <space> characters cause some
|
|
// imap servers to return an error.
|
|
const char *msg_SearchAdapter::m_kImapBefore = " SENTBEFORE ";
|
|
const char *msg_SearchAdapter::m_kImapBody = " BODY ";
|
|
const char *msg_SearchAdapter::m_kImapCC = " CC ";
|
|
const char *msg_SearchAdapter::m_kImapFrom = " FROM ";
|
|
const char *msg_SearchAdapter::m_kImapNot = " NOT";
|
|
const char *msg_SearchAdapter::m_kImapDeleted = " DELETED";
|
|
const char *msg_SearchAdapter::m_kImapOr = " OR";
|
|
const char *msg_SearchAdapter::m_kImapSince = " SENTSINCE ";
|
|
const char *msg_SearchAdapter::m_kImapSubject = " SUBJECT ";
|
|
const char *msg_SearchAdapter::m_kImapTo = " TO ";
|
|
const char *msg_SearchAdapter::m_kImapHeader = " HEADER";
|
|
const char *msg_SearchAdapter::m_kImapAnyText = " TEXT ";
|
|
const char *msg_SearchAdapter::m_kImapKeyword = " KEYWORD ";
|
|
const char *msg_SearchAdapter::m_kNntpKeywords = " KEYWORDS ";
|
|
const char *msg_SearchAdapter::m_kImapSentOn = " SENTON ";
|
|
const char *msg_SearchAdapter::m_kImapSeen = " SEEN ";
|
|
const char *msg_SearchAdapter::m_kImapAnswered = " ANSWERED ";
|
|
const char *msg_SearchAdapter::m_kImapNotSeen = " UNSEEN ";
|
|
const char *msg_SearchAdapter::m_kImapNotAnswered = " UNANSWERED ";
|
|
const char *msg_SearchAdapter::m_kImapCharset = " CHARSET ";
|
|
|
|
char *
|
|
msg_SearchAdapter::TryToConvertCharset(char *sourceStr, int16 src_csid, int16 dest_csid, XP_Bool useMime2)
|
|
{
|
|
char *result = NULL;
|
|
|
|
if (sourceStr == NULL)
|
|
return NULL;
|
|
|
|
if ((src_csid != dest_csid) || (useMime2))
|
|
{
|
|
// Need to convert. See if we can.
|
|
|
|
// ### mwelch Much of this code is taken from
|
|
// lib/libi18n/mime2fun.c (in particular,
|
|
// intl_EncodeMimePartIIStr).
|
|
CCCDataObject obj = NULL;
|
|
CCCFunc cvtfunc = NULL;
|
|
int srcLen = XP_STRLEN(sourceStr);
|
|
|
|
obj = INTL_CreateCharCodeConverter();
|
|
if (obj == NULL)
|
|
return 0;
|
|
|
|
/* setup converter from src_csid --> dest_csid */
|
|
INTL_GetCharCodeConverter(src_csid, dest_csid, obj) ;
|
|
cvtfunc = INTL_GetCCCCvtfunc(obj);
|
|
|
|
if (cvtfunc)
|
|
{
|
|
// We can convert, so do it.
|
|
if (useMime2)
|
|
// Force MIME-2 encoding so that the charset (if necessary) gets
|
|
// passed to the RFC977bis server inline
|
|
result = INTL_EncodeMimePartIIStr(sourceStr, src_csid, TRUE);
|
|
else
|
|
{
|
|
// Copy the source string before using it for conversion.
|
|
// You just don't know where those bits have been.
|
|
char *temp = XP_STRDUP(sourceStr);
|
|
if (temp)
|
|
{
|
|
// (result) will differ from (temp) iff a larger string
|
|
// were needed to contain the converted chars.
|
|
// (or so I understand)
|
|
result = (char *) cvtfunc(obj,
|
|
(unsigned char*)temp,
|
|
srcLen);
|
|
if (result != temp)
|
|
XP_FREE(temp);
|
|
}
|
|
}
|
|
}
|
|
XP_FREEIF(obj);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
char *
|
|
msg_SearchAdapter::GetImapCharsetParam(int16 dest_csid)
|
|
{
|
|
char *result = NULL;
|
|
|
|
// Specify a character set unless we happen to be US-ASCII.
|
|
if (! ((dest_csid == CS_ASCII) ||
|
|
(dest_csid == CS_UNKNOWN)))
|
|
{
|
|
char csname[128];
|
|
INTL_CharSetIDToName(dest_csid, csname);
|
|
result = PR_smprintf("%s%s", msg_SearchAdapter::m_kImapCharset, csname);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void
|
|
msg_SearchAdapter::GetSearchCSIDs(int16& src_csid, int16& dst_csid)
|
|
{
|
|
src_csid = CS_DEFAULT;
|
|
dst_csid = CS_DEFAULT;
|
|
|
|
if (m_scope)
|
|
{
|
|
if (m_scope->m_frame)
|
|
{
|
|
// If we can get the source charset from a context,
|
|
// that is preferable to just using the app default.
|
|
MWContext *ctxt = m_scope->m_frame->GetContext();
|
|
if (ctxt) {
|
|
INTL_CharSetInfo c = LO_GetDocumentCharacterSetInfo(ctxt);
|
|
src_csid = INTL_GetCSIWinCSID(c);
|
|
}
|
|
}
|
|
|
|
// Ask the newsgroup/folder for its csid.
|
|
if (m_scope->m_folder)
|
|
dst_csid = m_scope->m_folder->GetFolderCSID() & ~CS_AUTO;
|
|
}
|
|
|
|
// default means that our best guess is to get the default window char set ID
|
|
if (src_csid == CS_DEFAULT)
|
|
{
|
|
src_csid = INTL_DefaultWinCharSetID(0);
|
|
XP_ASSERT(src_csid != CS_DEFAULT);
|
|
}
|
|
|
|
|
|
// If
|
|
// the destination is still CS_DEFAULT, make the destination match
|
|
// the source. (CS_DEFAULT is an indication that the charset
|
|
// was undefined or unavailable.)
|
|
if (dst_csid == CS_DEFAULT)
|
|
dst_csid = src_csid;
|
|
|
|
XP_Bool forceAscii = FALSE;
|
|
PREF_GetBoolPref("mailnews.force_ascii_search", &forceAscii);
|
|
if (forceAscii)
|
|
{
|
|
// Special cases to use in order to force US-ASCII searching with Latin1
|
|
// or MacRoman text. This only has to happen because IMAP
|
|
// and RFC977bis servers currently (4/23/97) only support US-ASCII.
|
|
//
|
|
// If the dest csid is ISO Latin 1 or MacRoman, attempt to convert the
|
|
// source text to US-ASCII. (Not for now.)
|
|
// if ((dst_csid == CS_LATIN1) || (dst_csid == CS_MAC_ROMAN))
|
|
dst_csid = CS_ASCII;
|
|
}
|
|
}
|
|
|
|
#ifdef XP_MAC
|
|
#include <ConditionalMacros.h>
|
|
|
|
#pragma global_optimizer on
|
|
#pragma optimization_level 4
|
|
|
|
#endif
|
|
|
|
MSG_SearchError msg_SearchAdapter::EncodeImapTerm (MSG_SearchTerm *term, XP_Bool reallyRFC977bis, int16 src_csid, int16 dest_csid, char **ppOutTerm)
|
|
{
|
|
MSG_SearchError err = SearchError_Success;
|
|
XP_Bool useNot = FALSE;
|
|
XP_Bool useQuotes = FALSE;
|
|
XP_Bool excludeHeader = FALSE;
|
|
XP_Bool ignoreValue = FALSE;
|
|
char *arbitraryHeader = NULL;
|
|
const char *whichMnemonic = NULL;
|
|
const char *orHeaderMnemonic = NULL;
|
|
|
|
*ppOutTerm = NULL;
|
|
|
|
if (term->m_operator == opDoesntContain || term->m_operator == opIsnt)
|
|
useNot = TRUE;
|
|
|
|
switch (term->m_attribute)
|
|
{
|
|
case attribToOrCC:
|
|
orHeaderMnemonic = m_kImapCC;
|
|
// fall through to case attribTo:
|
|
case attribTo:
|
|
whichMnemonic = m_kImapTo;
|
|
break;
|
|
case attribCC:
|
|
whichMnemonic = m_kImapCC;
|
|
break;
|
|
case attribSender:
|
|
whichMnemonic = m_kImapFrom;
|
|
break;
|
|
case attribSubject:
|
|
whichMnemonic = m_kImapSubject;
|
|
break;
|
|
case attribBody:
|
|
whichMnemonic = m_kImapBody;
|
|
excludeHeader = TRUE;
|
|
break;
|
|
case attribOtherHeader: // arbitrary header? if so create arbitrary header string
|
|
if (term->m_arbitraryHeader)
|
|
{
|
|
arbitraryHeader = new char [XP_STRLEN(term->m_arbitraryHeader) + 6]; // 6 bytes for SPACE \" .... \" SPACE
|
|
if (!arbitraryHeader)
|
|
return SearchError_InvalidSearchTerm;
|
|
arbitraryHeader[0] = '\0';
|
|
XP_STRCAT(arbitraryHeader, " \"");
|
|
XP_STRCAT(arbitraryHeader, term->m_arbitraryHeader);
|
|
XP_STRCAT(arbitraryHeader, "\" ");
|
|
whichMnemonic = arbitraryHeader;
|
|
}
|
|
else
|
|
return SearchError_InvalidSearchTerm;
|
|
break;
|
|
case attribAgeInDays: // added for searching online for age in days...
|
|
// for AgeInDays, we are actually going to perform a search by date, so convert the operations for age
|
|
// to the IMAP mnemonics that we would use for date!
|
|
switch (term->m_operator)
|
|
{
|
|
case opIsGreaterThan:
|
|
whichMnemonic = m_kImapBefore;
|
|
break;
|
|
case opIsLessThan:
|
|
whichMnemonic = m_kImapSince;
|
|
break;
|
|
case opIs:
|
|
whichMnemonic = m_kImapSentOn;
|
|
break;
|
|
default:
|
|
XP_ASSERT(FALSE);
|
|
return SearchError_InvalidSearchTerm;
|
|
}
|
|
excludeHeader = TRUE;
|
|
break;
|
|
case attribDate:
|
|
switch (term->m_operator)
|
|
{
|
|
case opIsBefore:
|
|
whichMnemonic = m_kImapBefore;
|
|
break;
|
|
case opIsAfter:
|
|
whichMnemonic = m_kImapSince;
|
|
break;
|
|
case opIs:
|
|
whichMnemonic = m_kImapSentOn;
|
|
break;
|
|
default:
|
|
XP_ASSERT(FALSE);
|
|
return SearchError_InvalidSearchTerm;
|
|
}
|
|
excludeHeader = TRUE;
|
|
break;
|
|
case attribAnyText:
|
|
whichMnemonic = m_kImapAnyText;
|
|
excludeHeader = TRUE;
|
|
break;
|
|
case attribKeywords:
|
|
whichMnemonic = m_kNntpKeywords;
|
|
break;
|
|
case attribMsgStatus:
|
|
useNot = FALSE; // NOT SEEN is wrong, but UNSEEN is right.
|
|
ignoreValue = TRUE; // the mnemonic is all we need
|
|
excludeHeader = TRUE;
|
|
switch (term->m_value.u.msgStatus)
|
|
{
|
|
case MSG_FLAG_READ:
|
|
whichMnemonic = term->m_operator == opIs ? m_kImapSeen : m_kImapNotSeen;
|
|
break;
|
|
case MSG_FLAG_REPLIED:
|
|
whichMnemonic = term->m_operator == opIs ? m_kImapAnswered : m_kImapNotAnswered;
|
|
break;
|
|
default:
|
|
XP_ASSERT(FALSE);
|
|
return SearchError_InvalidSearchTerm;
|
|
}
|
|
break;
|
|
default:
|
|
XP_ASSERT(FALSE);
|
|
return SearchError_InvalidSearchTerm;
|
|
}
|
|
|
|
char *value = "";
|
|
char dateBuf[100];
|
|
dateBuf[0] = '\0';
|
|
|
|
XP_Bool valueWasAllocated = FALSE;
|
|
if (term->m_attribute == attribDate)
|
|
{
|
|
// note that there used to be code here that encoded an RFC822 date for imap searches.
|
|
// RFC 2060 seems to require an RFC822
|
|
// date but really it expects dd-mmm-yyyy, and refers to the RFC822 date only in that the
|
|
// dd-mmm-yyyy date will match the RFC822 date within the message.
|
|
strftime (dateBuf, sizeof(dateBuf), "%d-%b-%Y", localtime (&term->m_value.u.date));
|
|
value = dateBuf;
|
|
}
|
|
else
|
|
{
|
|
if (term->m_attribute == attribAgeInDays)
|
|
{
|
|
// okay, take the current date, subtract off the age in days, then do an appropriate Date search on
|
|
// the resulting day.
|
|
time_t now = XP_TIME();
|
|
time_t matchDay = now - term->m_value.u.age * 60 * 60 * 24;
|
|
strftime (dateBuf, sizeof(dateBuf), "%d-%b-%Y", localtime (&matchDay));
|
|
value = dateBuf;
|
|
}
|
|
else
|
|
|
|
if (IsStringAttribute(term->m_attribute))
|
|
{
|
|
char *unconvertedValue = reallyRFC977bis ? MSG_EscapeSearchUrl (term->m_value.u.string) : msg_EscapeImapSearchProtocol(term->m_value.u.string);
|
|
// Switch for Korean mail/news charsets.
|
|
// We want to do this here because here is where
|
|
// we know what charset we want to use.
|
|
if (reallyRFC977bis)
|
|
dest_csid = INTL_DefaultNewsCharSetID(dest_csid);
|
|
else
|
|
dest_csid = INTL_DefaultMailCharSetID(dest_csid);
|
|
|
|
value = TryToConvertCharset(unconvertedValue,
|
|
src_csid,
|
|
dest_csid,
|
|
reallyRFC977bis);
|
|
if (value)
|
|
XP_FREEIF(unconvertedValue);
|
|
else
|
|
value = unconvertedValue; // couldn't convert, send as is
|
|
|
|
valueWasAllocated = TRUE;
|
|
useQuotes = !reallyRFC977bis || (XP_STRCHR(value, ' ') != NULL);
|
|
}
|
|
}
|
|
|
|
int len = XP_STRLEN(whichMnemonic) + XP_STRLEN(value) + (useNot ? XP_STRLEN(m_kImapNot) : 0) +
|
|
(useQuotes ? 2 : 0) + XP_STRLEN(m_kImapHeader) +
|
|
(orHeaderMnemonic ? (XP_STRLEN(m_kImapHeader) + XP_STRLEN(m_kImapOr) + (useNot ? XP_STRLEN(m_kImapNot) : 0) +
|
|
XP_STRLEN(orHeaderMnemonic) + XP_STRLEN(value) + 2 /*""*/) : 0) + 1;
|
|
char *encoding = new char[len];
|
|
if (encoding)
|
|
{
|
|
encoding[0] = '\0';
|
|
// Remember: if ToOrCC and useNot then the expression becomes NOT To AND Not CC as opposed to (NOT TO) || (NOT CC)
|
|
if (orHeaderMnemonic && !useNot)
|
|
XP_STRCAT(encoding, m_kImapOr);
|
|
if (useNot)
|
|
XP_STRCAT (encoding, m_kImapNot);
|
|
if (!excludeHeader)
|
|
XP_STRCAT (encoding, m_kImapHeader);
|
|
XP_STRCAT (encoding, whichMnemonic);
|
|
if (!ignoreValue)
|
|
err = EncodeImapValue(encoding, value, useQuotes, reallyRFC977bis);
|
|
|
|
if (orHeaderMnemonic)
|
|
{
|
|
if (useNot)
|
|
XP_STRCAT(encoding, m_kImapNot);
|
|
|
|
XP_STRCAT (encoding, m_kImapHeader);
|
|
|
|
XP_STRCAT (encoding, orHeaderMnemonic);
|
|
if (!ignoreValue)
|
|
err = EncodeImapValue(encoding, value, useQuotes, reallyRFC977bis);
|
|
}
|
|
|
|
// don't let the encoding end with whitespace,
|
|
// this throws off later url STRCMP
|
|
if (*encoding && *(encoding + XP_STRLEN(encoding) - 1) == ' ')
|
|
*(encoding + XP_STRLEN(encoding) - 1) = '\0';
|
|
}
|
|
|
|
if (value && valueWasAllocated)
|
|
XP_FREE (value);
|
|
if (arbitraryHeader)
|
|
delete arbitraryHeader;
|
|
|
|
*ppOutTerm = encoding;
|
|
|
|
return err;
|
|
}
|
|
|
|
#ifdef XP_MAC
|
|
#pragma global_optimizer reset
|
|
#endif
|
|
|
|
MSG_SearchError msg_SearchAdapter::EncodeImapValue(char *encoding, const char *value, XP_Bool useQuotes, XP_Bool reallyRFC977bis)
|
|
{
|
|
// By NNTP RFC, SEARCH HEADER SUBJECT "" is legal and means 'find messages without a subject header'
|
|
if (!reallyRFC977bis)
|
|
{
|
|
// By IMAP RFC, SEARCH HEADER SUBJECT "" is illegal and will generate an error from the server
|
|
if (!value || !value[0])
|
|
return SearchError_ValueRequired;
|
|
}
|
|
|
|
if (useQuotes)
|
|
XP_STRCAT(encoding, "\"");
|
|
XP_STRCAT (encoding, value);
|
|
if (useQuotes)
|
|
XP_STRCAT(encoding, "\"");
|
|
|
|
return SearchError_Success;
|
|
}
|
|
|
|
|
|
MSG_SearchError msg_SearchAdapter::EncodeImap (char **ppOutEncoding, MSG_SearchTermArray &searchTerms, int16 src_csid, int16 dest_csid, XP_Bool reallyRFC977bis)
|
|
{
|
|
// i've left the old code (before using CBoolExpression for debugging purposes to make sure that
|
|
// the new code generates the same encoding string as the old code.....
|
|
|
|
MSG_SearchError err = SearchError_Success;
|
|
*ppOutEncoding = NULL;
|
|
|
|
int termCount = searchTerms.GetSize();
|
|
int i = 0;
|
|
int encodingLength = 0;
|
|
|
|
// Build up an array of encodings, one per query term
|
|
char **termEncodings = new char *[termCount];
|
|
if (!termEncodings)
|
|
return SearchError_OutOfMemory;
|
|
// create our expression
|
|
CBoolExpression * expression = new CBoolExpression();
|
|
if (!expression)
|
|
return SearchError_OutOfMemory;
|
|
|
|
for (i = 0; i < termCount && SearchError_Success == err; i++)
|
|
{
|
|
err = EncodeImapTerm (searchTerms.GetAt(i), reallyRFC977bis, src_csid, dest_csid, &termEncodings[i]);
|
|
if (SearchError_Success == err && NULL != termEncodings[i])
|
|
{
|
|
encodingLength += XP_STRLEN(termEncodings[i]) + 1;
|
|
expression = expression->AddSearchTerm(searchTerms.GetAt(i),termEncodings[i]);
|
|
}
|
|
}
|
|
|
|
if (SearchError_Success == err)
|
|
{
|
|
// Catenate the intermediate encodings together into a big string
|
|
char *totalEncoding = new char [encodingLength + (!reallyRFC977bis ? (XP_STRLEN(m_kImapNot) + XP_STRLEN(m_kImapDeleted)) : 0) + 1];
|
|
int32 encodingBuffSize = expression->CalcEncodeStrSize() + (!reallyRFC977bis ? (XP_STRLEN(m_kImapNot) + XP_STRLEN(m_kImapDeleted)) : 0) + 1;
|
|
char *encodingBuff = new char [encodingBuffSize];
|
|
|
|
if (encodingBuff && totalEncoding)
|
|
{
|
|
totalEncoding[0] = '\0';
|
|
encodingBuff[0] = '\0';
|
|
|
|
int offset = 0; // offset into starting place for the buffer
|
|
if (!reallyRFC977bis)
|
|
{
|
|
XP_STRCAT(totalEncoding, m_kImapNot);
|
|
XP_STRCAT(totalEncoding, m_kImapDeleted);
|
|
}
|
|
|
|
if (!reallyRFC977bis)
|
|
{
|
|
XP_STRCAT(encodingBuff, m_kImapNot);
|
|
XP_STRCAT(encodingBuff, m_kImapDeleted);
|
|
offset = XP_STRLEN(m_kImapDeleted) + XP_STRLEN(m_kImapNot);
|
|
}
|
|
|
|
expression->GenerateEncodeStr(encodingBuff+offset,encodingBuffSize);
|
|
|
|
for (i = 0; i < termCount; i++)
|
|
{
|
|
if (termEncodings[i])
|
|
{
|
|
XP_STRCAT (totalEncoding, termEncodings[i]);
|
|
delete [] termEncodings[i];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
err = SearchError_OutOfMemory;
|
|
|
|
delete totalEncoding;
|
|
delete expression;
|
|
|
|
// Set output parameter if we encoded the query successfully
|
|
if (encodingBuff && SearchError_Success == err)
|
|
*ppOutEncoding = encodingBuff;
|
|
}
|
|
|
|
delete [] termEncodings;
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
char *msg_SearchAdapter::TransformSpacesToStars (const char *spaceString)
|
|
{
|
|
char *starString = XP_STRDUP (spaceString);
|
|
if (starString)
|
|
{
|
|
char *star = NULL;
|
|
while (1)
|
|
{
|
|
star = XP_STRCHR(starString, ' ');
|
|
if (!star)
|
|
break;
|
|
*star = '*';
|
|
}
|
|
}
|
|
return starString;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-------------------- Implementation of MSG_BodyHandler ----------------------
|
|
//-----------------------------------------------------------------------------
|
|
|
|
MSG_BodyHandler::MSG_BodyHandler (MSG_ScopeTerm * scope, uint32 offset, uint32 numLines, DBMessageHdr * msg, MessageDB * db)
|
|
{
|
|
m_scope = scope;
|
|
m_localFileOffset = offset;
|
|
m_numLocalLines = numLines;
|
|
m_msgHdr = msg;
|
|
m_db = db;
|
|
|
|
// the following are variables used when the body handler is handling stuff from filters....through this constructor, that is not the
|
|
// case so we set them to NULL.
|
|
m_headers = NULL;
|
|
m_headersSize = 0;
|
|
m_Filtering = FALSE; // make sure we set this before we call initialize...
|
|
|
|
Initialize(); // common initialization stuff
|
|
|
|
if (m_scope->IsOfflineIMAPMail() || m_scope->m_folder->GetType() == FOLDER_IMAPMAIL) // if we are here with an IMAP folder, assume offline!
|
|
m_OfflineIMAP = TRUE;
|
|
else
|
|
OpenLocalFolder(); // POP so open the mail folder file
|
|
}
|
|
|
|
MSG_BodyHandler::MSG_BodyHandler(MSG_ScopeTerm * scope, uint32 offset, uint32 numLines, DBMessageHdr * msg, MessageDB * db,
|
|
char * headers, uint32 headersSize, XP_Bool Filtering)
|
|
{
|
|
m_scope = scope;
|
|
m_localFileOffset = offset;
|
|
m_numLocalLines = numLines;
|
|
m_msgHdr = msg;
|
|
m_db = db;
|
|
m_headersSize = headersSize;
|
|
m_Filtering = Filtering;
|
|
|
|
Initialize();
|
|
|
|
if (m_Filtering)
|
|
m_headers = headers;
|
|
else
|
|
if (m_scope->IsOfflineIMAPMail() || m_scope->m_folder->GetType() == FOLDER_IMAPMAIL)
|
|
m_OfflineIMAP = TRUE;
|
|
else
|
|
OpenLocalFolder(); // if nothing else applies, then we must be a POP folder file
|
|
}
|
|
|
|
void MSG_BodyHandler::Initialize()
|
|
// common initialization code regardless of what body type we are handling...
|
|
{
|
|
// Default transformations for local message search and MAPI access
|
|
m_stripHeaders = TRUE;
|
|
m_stripHtml = TRUE;
|
|
m_messageIsHtml = FALSE;
|
|
m_passedHeaders = FALSE;
|
|
|
|
// set our offsets to 0 since we haven't handled any bytes yet...
|
|
m_IMAPMessageOffset = 0;
|
|
m_NewsArticleOffset = 0;
|
|
m_headerBytesRead = 0;
|
|
|
|
m_OfflineIMAP = FALSE;
|
|
}
|
|
|
|
MSG_BodyHandler::~MSG_BodyHandler()
|
|
{
|
|
if (m_scope->m_file)
|
|
{
|
|
XP_FileClose (m_scope->m_file);
|
|
m_scope->m_file = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
void MSG_BodyHandler::OpenLocalFolder()
|
|
{
|
|
if (!m_scope->m_file)
|
|
{
|
|
const char *path = m_scope->GetMailPath();
|
|
if (path)
|
|
m_scope->m_file = XP_FileOpen (path, xpMailFolder, XP_FILE_READ_BIN); // open the folder
|
|
}
|
|
if (m_scope->m_file)
|
|
XP_FileSeek (m_scope->m_file, m_localFileOffset, SEEK_SET);
|
|
}
|
|
|
|
|
|
int32 MSG_BodyHandler::GetNextLine (char * buf, int bufSize)
|
|
{
|
|
int32 length = 0;
|
|
XP_Bool eatThisLine = FALSE;
|
|
|
|
do {
|
|
// first, handle the filtering case...this is easy....
|
|
if (m_Filtering)
|
|
length = GetNextFilterLine(buf, bufSize);
|
|
else
|
|
{
|
|
// 3 cases: Offline IMAP, POP, or we are dealing with a news message....
|
|
if (m_db)
|
|
{
|
|
MailDB * mailDB = m_db->GetMailDB();
|
|
if (mailDB) // a mail data base?
|
|
{
|
|
if (m_OfflineIMAP)
|
|
length = GetNextIMAPLine (buf, bufSize); // (1) IMAP Offline
|
|
else
|
|
length = GetNextLocalLine (buf, bufSize); // (2) POP
|
|
}
|
|
else
|
|
{
|
|
NewsGroupDB * newsDB = m_db->GetNewsDB();
|
|
if (newsDB)
|
|
length = GetNextNewsLine (newsDB, buf, bufSize); // (3) News
|
|
}
|
|
}
|
|
}
|
|
|
|
if (length > 0)
|
|
length = ApplyTransformations (buf, length, eatThisLine);
|
|
} while (eatThisLine && length); // if we hit eof, make sure we break out of this loop.
|
|
|
|
return length;
|
|
}
|
|
|
|
|
|
int32 MSG_BodyHandler::GetNextFilterLine(char * buf, int bufSize)
|
|
{
|
|
// m_nextHdr always points to the next header in the list....the list is NULL terminated...
|
|
int numBytesCopied = 0;
|
|
if (m_headersSize > 0)
|
|
{
|
|
// #mscott. hack. filter headers list have CRs & LFs inside the NULL delimited list of header
|
|
// strings. It is possible to have: To NULL CR LF From. We want to skip over these CR/LFs if they start
|
|
// at the beginning of what we think is another header.
|
|
while ((m_headers[0] == CR || m_headers[0] == LF || m_headers[0] == ' ' || m_headers[0] == '\0') && m_headersSize > 0)
|
|
{
|
|
m_headers++; // skip over these chars...
|
|
m_headersSize--;
|
|
}
|
|
|
|
if (m_headersSize > 0)
|
|
{
|
|
numBytesCopied = XP_STRLEN(m_headers)+1 /* + 1 to include NULL */ < bufSize ? XP_STRLEN(m_headers)+1 : bufSize;
|
|
XP_MEMCPY(buf, m_headers, numBytesCopied);
|
|
m_headers += numBytesCopied;
|
|
// be careful...m_headersSize is unsigned. Don't let it go negative or we overflow to 2^32....*yikes*
|
|
if (m_headersSize < numBytesCopied)
|
|
m_headersSize = 0;
|
|
else
|
|
m_headersSize -= numBytesCopied; // update # bytes we have read from the headers list
|
|
|
|
return numBytesCopied;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int32 MSG_BodyHandler::GetNextNewsLine (NewsGroupDB * /* newsDB */, char * buf, int bufSize)
|
|
{
|
|
// we know we have a safe downcasting on m_msgHdr to a NewsMessageHdr because we checked that
|
|
// m_db is a news data base before calling this routine
|
|
int32 msgLength = ((NewsMessageHdr *)m_msgHdr)->GetOfflineMessageLength (m_db->GetDB()) - m_NewsArticleOffset;
|
|
if (buf && msgLength != 0) // make sure the news article exists....
|
|
{
|
|
int32 bytesToCopy = (msgLength < bufSize-2) ? msgLength : bufSize - 2; // this -2 is a small hack
|
|
int32 bytesCopied = ((NewsMessageHdr *)m_msgHdr)->ReadFromArticle (buf, bytesToCopy, m_NewsArticleOffset, m_db->GetDB());
|
|
if (bytesCopied == 0) // reached end of message?
|
|
return bytesCopied;
|
|
|
|
// now determine the location of the nearest CR/LF pairing...
|
|
char * tmp = strncasestr (buf, "\x0D\x0A", bytesCopied); // get pointer to end of line
|
|
if (tmp)
|
|
// a line is contained within the buffer. Null terminate 2 positions past the CR/LF pair, update new offset value
|
|
// we know we have at least 2 bytes leftover in the buffer
|
|
*(tmp+2) = '\0'; // null terminate string after CR/LF
|
|
else
|
|
buf[bytesCopied] = '\0';
|
|
m_NewsArticleOffset += XP_STRLEN (buf);
|
|
return XP_STRLEN (buf); // return num bytes stored in the buf
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int32 MSG_BodyHandler::GetNextLocalLine(char * buf, int bufSize)
|
|
// returns number of bytes copied
|
|
{
|
|
char * line = NULL;
|
|
if (m_numLocalLines)
|
|
{
|
|
if (m_passedHeaders)
|
|
m_numLocalLines--; // the line count is only for body lines
|
|
line = XP_FileReadLine (buf, bufSize, m_scope->m_file);
|
|
if (line)
|
|
return XP_STRLEN(line);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int32 MSG_BodyHandler::GetNextIMAPLine(char * buf, int bufSize)
|
|
{
|
|
// we know we have safe downcasting on m_msgHdr because we checked that m_db is a mail data base before calling
|
|
// this routine.
|
|
int32 msgLength = ((MailMessageHdr *) m_msgHdr)->GetOfflineMessageLength (m_db->GetDB()) - m_IMAPMessageOffset;
|
|
if (buf && msgLength != 0) // make sure message exists
|
|
{
|
|
int32 bytesToCopy = (msgLength < bufSize-2) ? msgLength : bufSize-2; // the -2 is a small hack
|
|
|
|
int32 bytesCopied = ((MailMessageHdr *) m_msgHdr)->ReadFromOfflineMessage (buf, bytesToCopy, m_IMAPMessageOffset, m_db->GetDB());
|
|
if (bytesCopied == 0) // we reached end of message
|
|
return bytesCopied;
|
|
|
|
// now determine the location of the nearest CR/LF pairing....
|
|
char * tmp = strncasestr (buf, "\x0D\x0A",bytesCopied); // get pointer to end of line
|
|
if (tmp)
|
|
// a line is contained within the buffer. Null terminate 2 positions past the CR/LF pair, update new offset value
|
|
// we know we have at least 2 bytes leftover in the buffer
|
|
*(tmp+2) = '\0'; // null terminate string after CR/LF
|
|
else
|
|
buf[bytesCopied] = '\0';
|
|
m_IMAPMessageOffset += XP_STRLEN (buf);
|
|
return XP_STRLEN (buf); // return num bytes stored in the buf
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int32 MSG_BodyHandler::ApplyTransformations (char *buf, int32 length, XP_Bool &eatThisLine)
|
|
{
|
|
int32 newLength = length;
|
|
eatThisLine = FALSE;
|
|
|
|
if (!m_passedHeaders) // buf is a line from the message headers
|
|
{
|
|
if (m_stripHeaders)
|
|
eatThisLine = TRUE;
|
|
|
|
if (!XP_STRNCASECMP(buf, "Content-Type:", 13) && strcasestr (buf, "text/html"))
|
|
m_messageIsHtml = TRUE;
|
|
|
|
m_passedHeaders = EMPTY_MESSAGE_LINE(buf);
|
|
}
|
|
else // buf is a line from the message body
|
|
{
|
|
if (m_stripHtml && m_messageIsHtml)
|
|
{
|
|
StripHtml (buf);
|
|
newLength = XP_STRLEN (buf);
|
|
}
|
|
}
|
|
|
|
return newLength;
|
|
}
|
|
|
|
|
|
void MSG_BodyHandler::StripHtml (char *pBufInOut)
|
|
{
|
|
char *pBuf = (char*) XP_ALLOC (XP_STRLEN(pBufInOut) + 1);
|
|
if (pBuf)
|
|
{
|
|
char *pWalk = pBuf;
|
|
char *pWalkInOut = pBufInOut;
|
|
XP_Bool inTag = FALSE;
|
|
while (*pWalkInOut) // throw away everything inside < >
|
|
{
|
|
if (!inTag)
|
|
if (*pWalkInOut == '<')
|
|
inTag = TRUE;
|
|
else
|
|
*pWalk++ = *pWalkInOut;
|
|
else
|
|
if (*pWalkInOut == '>')
|
|
inTag = FALSE;
|
|
pWalkInOut++;
|
|
}
|
|
*pWalk = 0; // null terminator
|
|
|
|
// copy the temp buffer back to the real one
|
|
pWalk = pBuf;
|
|
pWalkInOut = pBufInOut;
|
|
while (*pWalk)
|
|
*pWalkInOut++ = *pWalk++;
|
|
*pWalkInOut = *pWalk; // null terminator
|
|
XP_FREE (pBuf);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-------------------- Implementation of MSG_SearchTerm -----------------------
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Needed for DeStream method.
|
|
MSG_SearchTerm::MSG_SearchTerm() : msg_OpaqueObject (m_expectedMagic)
|
|
{
|
|
m_arbitraryHeader = NULL;
|
|
}
|
|
|
|
|
|
|
|
MSG_SearchTerm::MSG_SearchTerm (
|
|
MSG_SearchAttribute attrib,
|
|
MSG_SearchOperator op,
|
|
MSG_SearchValue *val,
|
|
XP_Bool BooleanAND,
|
|
char * arbitraryHeader) : msg_OpaqueObject (m_expectedMagic)
|
|
{
|
|
m_operator = op;
|
|
m_booleanOp = (BooleanAND) ? MSG_SearchBooleanAND : MSG_SearchBooleanOR;
|
|
if (attrib == attribOtherHeader && arbitraryHeader)
|
|
m_arbitraryHeader = XP_STRDUP(arbitraryHeader);
|
|
else
|
|
m_arbitraryHeader = NULL;
|
|
m_attribute = attrib;
|
|
|
|
MSG_ResultElement::AssignValues (val, &m_value);
|
|
}
|
|
|
|
MSG_SearchTerm::MSG_SearchTerm (
|
|
MSG_SearchAttribute attrib,
|
|
MSG_SearchOperator op,
|
|
MSG_SearchValue *val,
|
|
MSG_SearchBooleanOp boolOp,
|
|
char * arbitraryHeader) : msg_OpaqueObject (m_expectedMagic)
|
|
{
|
|
m_operator = op;
|
|
m_attribute = attrib;
|
|
m_booleanOp = boolOp;
|
|
if (attrib == attribOtherHeader && arbitraryHeader)
|
|
m_arbitraryHeader = XP_STRDUP(arbitraryHeader);
|
|
else
|
|
m_arbitraryHeader = NULL;
|
|
|
|
MSG_ResultElement::AssignValues (val, &m_value);
|
|
}
|
|
|
|
|
|
|
|
MSG_SearchTerm::~MSG_SearchTerm ()
|
|
{
|
|
if (IsStringAttribute (m_attribute))
|
|
XP_FREE(m_value.u.string);
|
|
if (m_arbitraryHeader)
|
|
XP_FREE(m_arbitraryHeader);
|
|
}
|
|
|
|
// Perhaps we could find a better place for this?
|
|
// Caller needs to free.
|
|
/* static */char *MSG_SearchTerm::EscapeQuotesInStr(const char *str)
|
|
{
|
|
int numQuotes = 0;
|
|
for (const char *strPtr = str; *strPtr; strPtr++)
|
|
if (*strPtr == '"')
|
|
numQuotes++;
|
|
int escapedStrLen = XP_STRLEN(str) + numQuotes;
|
|
char *escapedStr = (char *) XP_ALLOC(escapedStrLen + 1);
|
|
if (escapedStr)
|
|
{
|
|
char *destPtr;
|
|
for (destPtr = escapedStr; *str; str++)
|
|
{
|
|
if (*str == '"')
|
|
*destPtr++ = '\\';
|
|
*destPtr++ = *str;
|
|
}
|
|
*destPtr = '\0';
|
|
}
|
|
return escapedStr;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_SearchTerm::OutputValue(XPStringObj &outputStr)
|
|
{
|
|
if (IsStringAttribute(m_attribute))
|
|
{
|
|
XP_Bool quoteVal = FALSE;
|
|
// need to quote strings with ')' - filter code will escape quotes
|
|
if (XP_STRCHR(m_value.u.string, ')'))
|
|
{
|
|
quoteVal = TRUE;
|
|
outputStr += "\"";
|
|
}
|
|
if (XP_STRCHR(m_value.u.string, '"'))
|
|
{
|
|
char *escapedString = MSG_SearchTerm::EscapeQuotesInStr(m_value.u.string);
|
|
if (escapedString)
|
|
{
|
|
outputStr += escapedString;
|
|
XP_FREE(escapedString);
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
outputStr += m_value.u.string;
|
|
}
|
|
if (quoteVal)
|
|
outputStr += "\"";
|
|
}
|
|
else
|
|
{
|
|
switch (m_attribute)
|
|
{
|
|
case attribDate:
|
|
{
|
|
struct tm *p = localtime(&m_value.u.date);
|
|
if (p)
|
|
{
|
|
// wow, so tm_mon is 0 based, tm_mday is 1 based.
|
|
char dateBuf[100];
|
|
strftime (dateBuf, sizeof(dateBuf), "%d-%b-%Y", p);
|
|
outputStr += dateBuf;
|
|
}
|
|
else
|
|
outputStr += "01/01/70";
|
|
break;
|
|
}
|
|
case attribMsgStatus:
|
|
{
|
|
char status[40];
|
|
MSG_GetStatusName (m_value.u.msgStatus, status, sizeof(status));
|
|
outputStr += status;
|
|
break;
|
|
}
|
|
case attribPriority:
|
|
{
|
|
char priority[40];
|
|
MSG_GetUntranslatedPriorityName( m_value.u.priority,
|
|
priority,
|
|
sizeof(priority));
|
|
outputStr += priority;
|
|
break;
|
|
}
|
|
default:
|
|
XP_ASSERT(FALSE);
|
|
break;
|
|
}
|
|
}
|
|
return SearchError_Success;
|
|
}
|
|
|
|
MSG_SearchError MSG_SearchTerm::EnStreamNew (char **outStream, int16 *length)
|
|
{
|
|
const char *attrib, *operatorStr;
|
|
XPStringObj outputStr;
|
|
MSG_SearchError ret;
|
|
|
|
ret = MSG_GetStringForAttribute(m_attribute, &attrib);
|
|
if (ret != SearchError_Success)
|
|
return ret;
|
|
|
|
if (m_attribute == attribOtherHeader) // if arbitrary header, use it instead!
|
|
{
|
|
outputStr = "\"";
|
|
if (m_arbitraryHeader)
|
|
outputStr += m_arbitraryHeader;
|
|
outputStr += "\"";
|
|
}
|
|
else
|
|
outputStr = attrib;
|
|
|
|
outputStr += ',';
|
|
|
|
ret = MSG_GetStringForOperator(m_operator, &operatorStr);
|
|
if (ret != SearchError_Success)
|
|
return ret;
|
|
|
|
outputStr += operatorStr;
|
|
outputStr += ',';
|
|
|
|
OutputValue(outputStr);
|
|
*length = XP_STRLEN(outputStr);
|
|
*outStream = (char *) XP_ALLOC(*length + 1);
|
|
if (*outStream)
|
|
{
|
|
XP_STRCPY(*outStream, outputStr);
|
|
return SearchError_Success;
|
|
}
|
|
else
|
|
return SearchError_OutOfMemory;
|
|
}
|
|
|
|
// fill in m_value from the input stream.
|
|
MSG_SearchError MSG_SearchTerm::ParseValue(char *inStream)
|
|
{
|
|
if (IsStringAttribute(m_attribute))
|
|
{
|
|
XP_Bool quoteVal = FALSE;
|
|
while (XP_IS_SPACE(*inStream))
|
|
inStream++;
|
|
|
|
// need to remove pair of '"', if present
|
|
if (*inStream == '"')
|
|
{
|
|
quoteVal = TRUE;
|
|
inStream++;
|
|
}
|
|
int valueLen = XP_STRLEN(inStream);
|
|
if (quoteVal && inStream[valueLen - 1] == '"')
|
|
valueLen--;
|
|
|
|
m_value.u.string = (char *) XP_ALLOC(valueLen + 1);
|
|
XP_STRNCPY_SAFE(m_value.u.string, inStream, valueLen + 1);
|
|
}
|
|
else
|
|
{
|
|
switch (m_attribute)
|
|
{
|
|
case attribDate:
|
|
m_value.u.date = XP_ParseTimeString (inStream, FALSE);
|
|
break;
|
|
case attribMsgStatus:
|
|
m_value.u.msgStatus = MSG_GetStatusValueFromName(inStream);
|
|
break;
|
|
case attribPriority:
|
|
m_value.u.priority = MSG_GetPriorityFromString(inStream);
|
|
break;
|
|
default:
|
|
XP_ASSERT(FALSE);
|
|
break;
|
|
}
|
|
}
|
|
m_value.attribute = m_attribute;
|
|
return SearchError_Success;
|
|
}
|
|
|
|
// find the operator code for this operator string.
|
|
MSG_SearchOperator MSG_SearchTerm::ParseOperator(char *inStream)
|
|
{
|
|
int16 operatorVal;
|
|
MSG_SearchError err;
|
|
|
|
while (XP_IS_SPACE(*inStream))
|
|
inStream++;
|
|
|
|
char *commaSep = XP_STRCHR(inStream, ',');
|
|
|
|
if (commaSep)
|
|
*commaSep = '\0';
|
|
|
|
err = MSG_GetOperatorFromString(inStream, &operatorVal);
|
|
return (MSG_SearchOperator) operatorVal;
|
|
}
|
|
|
|
// find the attribute code for this comma-delimited attribute.
|
|
MSG_SearchAttribute MSG_SearchTerm::ParseAttribute(char *inStream)
|
|
{
|
|
XPStringObj attributeStr;
|
|
int16 attributeVal;
|
|
MSG_SearchError err;
|
|
|
|
while (XP_IS_SPACE(*inStream))
|
|
inStream++;
|
|
|
|
// if we are dealing with an arbitrary header, it may be quoted....
|
|
XP_Bool quoteVal = FALSE;
|
|
if (*inStream == '"')
|
|
{
|
|
quoteVal = TRUE;
|
|
inStream++;
|
|
}
|
|
|
|
char *separator;
|
|
if (quoteVal) // arbitrary headers are quoted...
|
|
separator = XP_STRCHR(inStream, '"');
|
|
else
|
|
separator = XP_STRCHR(inStream, ',');
|
|
|
|
if (separator)
|
|
*separator = '\0';
|
|
|
|
err = MSG_GetAttributeFromString(inStream, &attributeVal);
|
|
MSG_SearchAttribute attrib = (MSG_SearchAttribute) attributeVal;
|
|
|
|
if (attrib == attribOtherHeader) // if we are dealing with an arbitrary header....
|
|
{
|
|
if (m_arbitraryHeader)
|
|
XP_FREE(m_arbitraryHeader);
|
|
m_arbitraryHeader = (char *) XP_ALLOC(XP_STRLEN(inStream)+1);
|
|
if (m_arbitraryHeader)
|
|
{
|
|
m_arbitraryHeader[0] = '\0';
|
|
XP_STRCAT(m_arbitraryHeader, inStream);
|
|
}
|
|
}
|
|
|
|
return attrib;
|
|
}
|
|
|
|
// De stream one search term. If the condition looks like
|
|
// condition = "(to or CC, contains, r-thompson) AND (body, doesn't contain, fred)"
|
|
// This routine should get called twice, the first time
|
|
// with "to or CC, contains, r-thompson", the second time with
|
|
// "body, doesn't contain, fred"
|
|
|
|
MSG_SearchError MSG_SearchTerm::DeStreamNew (char *inStream, int16 /*length*/)
|
|
{
|
|
char *commaSep = XP_STRCHR(inStream, ',');
|
|
m_attribute = ParseAttribute(inStream); // will allocate space for arbitrary header if necessary
|
|
if (!commaSep)
|
|
return SearchError_InvalidSearchTerm;
|
|
char *secondCommaSep = XP_STRCHR(commaSep + 1, ',');
|
|
if (commaSep)
|
|
m_operator = ParseOperator(commaSep + 1);
|
|
if (secondCommaSep)
|
|
ParseValue(secondCommaSep + 1);
|
|
return SearchError_Success;
|
|
}
|
|
|
|
|
|
void MSG_SearchTerm::StripQuotedPrintable (unsigned char *src)
|
|
{
|
|
// decode quoted printable text in place
|
|
|
|
unsigned char *dest = src;
|
|
int srcIdx = 0, destIdx = 0;
|
|
|
|
while (src[srcIdx] != 0)
|
|
{
|
|
if (src[srcIdx] == '=')
|
|
{
|
|
unsigned char *token = &src[srcIdx];
|
|
unsigned char c = 0;
|
|
|
|
// decode the first quoted char
|
|
if (token[1] >= '0' && token[1] <= '9')
|
|
c = token[1] - '0';
|
|
else if (token[1] >= 'A' && token[1] <= 'F')
|
|
c = token[1] - ('A' - 10);
|
|
else if (token[1] >= 'a' && token[1] <= 'f')
|
|
c = token[1] - ('a' - 10);
|
|
else
|
|
{
|
|
// first char after '=' isn't hex. copy the '=' as a normal char and keep going
|
|
dest[destIdx++] = src[srcIdx++]; // aka token[0]
|
|
continue;
|
|
}
|
|
|
|
// decode the second quoted char
|
|
c = (c << 4);
|
|
if (token[2] >= '0' && token[2] <= '9')
|
|
c += token[2] - '0';
|
|
else if (token[2] >= 'A' && token[2] <= 'F')
|
|
c += token[2] - ('A' - 10);
|
|
else if (token[2] >= 'a' && token[2] <= 'f')
|
|
c += token[2] - ('a' - 10);
|
|
else
|
|
{
|
|
// second char after '=' isn't hex. copy the '=' as a normal char and keep going
|
|
dest[destIdx++] = src[srcIdx++]; // aka token[0]
|
|
continue;
|
|
}
|
|
|
|
// if we got here, we successfully decoded a quoted printable sequence,
|
|
// so bump each pointer past it and move on to the next char;
|
|
dest[destIdx++] = c;
|
|
srcIdx += 3;
|
|
|
|
}
|
|
else
|
|
dest[destIdx++] = src[srcIdx++];
|
|
}
|
|
|
|
dest[destIdx] = src[srcIdx]; // null terminate
|
|
}
|
|
|
|
// Looks in the MessageDB for the user specified arbitrary header, if it finds the header, it then looks for a match against
|
|
// the value for the header.
|
|
MSG_SearchError MSG_SearchTerm::MatchArbitraryHeader (MSG_ScopeTerm *scope, uint32 offset, uint32 length /* in lines*/, int16 foldcsid,
|
|
DBMessageHdr *msg, MessageDB * db, char * headers, uint32 headersSize, XP_Bool ForFiltering)
|
|
{
|
|
if (!m_arbitraryHeader)
|
|
return SearchError_NotAMatch;
|
|
|
|
MSG_SearchError err = SearchError_NotAMatch;
|
|
MSG_BodyHandler * bodyHan = new MSG_BodyHandler (scope, offset,length, msg, db, headers, headersSize, ForFiltering);
|
|
if (!bodyHan)
|
|
return SearchError_OutOfMemory;
|
|
|
|
bodyHan->SetStripHeaders (FALSE);
|
|
|
|
if (MatchAllBeforeDeciding())
|
|
err = SearchError_Success;
|
|
else
|
|
err = SearchError_NotAMatch;
|
|
|
|
const int kBufSize = 512; // max size of a line??
|
|
char * buf = (char *) XP_ALLOC(kBufSize);
|
|
if (buf)
|
|
{
|
|
XP_Bool searchingHeaders = TRUE;
|
|
while (searchingHeaders && bodyHan->GetNextLine(buf, kBufSize))
|
|
{
|
|
if (!XP_STRNCASECMP(buf,m_arbitraryHeader, XP_STRLEN(m_arbitraryHeader)))
|
|
{
|
|
if (XP_STRLEN(m_arbitraryHeader) < XP_STRLEN(buf)) // make sure buf has info besides just the header
|
|
{
|
|
MSG_SearchError err2 = MatchString(buf+XP_STRLEN(m_arbitraryHeader), foldcsid); // match value with the other info...
|
|
if (err != err2) // if we found a match
|
|
{
|
|
searchingHeaders = FALSE; // then stop examining the headers
|
|
err = err2;
|
|
}
|
|
}
|
|
}
|
|
if (buf[0] == CR || buf[0] == LF || buf[0] == '\0')
|
|
searchingHeaders = FALSE;
|
|
}
|
|
delete bodyHan;
|
|
XP_FREE(buf);
|
|
return err;
|
|
}
|
|
else
|
|
{
|
|
delete bodyHan;
|
|
return SearchError_OutOfMemory;
|
|
}
|
|
}
|
|
|
|
MSG_SearchError MSG_SearchTerm::MatchBody (MSG_ScopeTerm *scope, uint32 offset, uint32 length /*in lines*/, int16 foldcsid,
|
|
DBMessageHdr *msg, MessageDB * db)
|
|
{
|
|
MSG_SearchError err = SearchError_NotAMatch;
|
|
|
|
// Small hack so we don't look all through a message when someone has
|
|
// specified "BODY IS foo"
|
|
if ((length > 0) && (m_operator == opIs || m_operator == opIsnt))
|
|
length = XP_STRLEN (m_value.u.string);
|
|
|
|
MSG_BodyHandler * bodyHan = new MSG_BodyHandler (scope, offset, length, msg, db);
|
|
if (!bodyHan)
|
|
return SearchError_OutOfMemory;
|
|
|
|
const int kBufSize = 512; // max size of a line???
|
|
char *buf = (char*) XP_ALLOC(kBufSize);
|
|
if (buf)
|
|
{
|
|
XP_Bool endOfFile = FALSE; // if retValue == 0, we've hit the end of the file
|
|
uint32 lines = 0;
|
|
|
|
CCCDataObject conv = INTL_CreateCharCodeConverter();
|
|
XP_Bool getConverter = FALSE;
|
|
int16 win_csid = INTL_DocToWinCharSetID(foldcsid);
|
|
int16 mail_csid = INTL_DefaultMailCharSetID(win_csid); // to default mail_csid (e.g. JIS for Japanese)
|
|
if ((NULL != conv) && INTL_GetCharCodeConverter(mail_csid, win_csid, conv))
|
|
getConverter = TRUE;
|
|
|
|
// Change the sense of the loop so we don't bail out prematurely
|
|
// on negative terms. i.e. opDoesntContain must look at all lines
|
|
MSG_SearchError errContinueLoop;
|
|
if (MatchAllBeforeDeciding())
|
|
err = errContinueLoop = SearchError_Success;
|
|
else
|
|
err = errContinueLoop = SearchError_NotAMatch;
|
|
|
|
// 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. ### FIX ME
|
|
// bug fix #88935: for stateful csids like JIS, we don't want to decode
|
|
// quoted printable since it contains '='.
|
|
XP_Bool isQuotedPrintable = !(mail_csid & STATEFUL) &&
|
|
(XP_STRCHR (m_value.u.string, '=') == NULL);
|
|
|
|
while (!endOfFile && err == errContinueLoop)
|
|
{
|
|
if (bodyHan->GetNextLine(buf, kBufSize))
|
|
{
|
|
// Do in-place decoding of quoted printable
|
|
if (isQuotedPrintable)
|
|
StripQuotedPrintable ((unsigned char*)buf);
|
|
|
|
char *compare = buf;
|
|
if (getConverter)
|
|
{
|
|
// In here we do I18N conversion if we get the converter
|
|
char *newBody = NULL;
|
|
newBody = (char *)INTL_CallCharCodeConverter(conv, (unsigned char *) buf, (int32) XP_STRLEN(buf));
|
|
if (newBody && (newBody != buf))
|
|
{
|
|
// CharCodeConverter return the char* to the orginal string
|
|
// we don't want to free body in that case
|
|
compare = newBody;
|
|
}
|
|
}
|
|
if (*compare && *compare != CR && *compare != LF)
|
|
{
|
|
err = MatchString (compare, win_csid, TRUE);
|
|
lines++;
|
|
}
|
|
if (compare != buf)
|
|
XP_FREEIF(compare);
|
|
}
|
|
else
|
|
endOfFile = TRUE;
|
|
}
|
|
|
|
if(conv)
|
|
INTL_DestroyCharCodeConverter(conv);
|
|
XP_FREEIF(buf);
|
|
delete bodyHan;
|
|
}
|
|
else
|
|
err = SearchError_OutOfMemory;
|
|
return err;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_SearchTerm::MatchString (const char *stringToMatch, int16 csid, XP_Bool body)
|
|
{
|
|
MSG_SearchError err = SearchError_NotAMatch;
|
|
unsigned char* n_str = NULL;
|
|
unsigned char* n_header = NULL;
|
|
if(opIsEmpty != m_operator) // Save some performance for opIsEmpty
|
|
{
|
|
n_str = INTL_GetNormalizeStr(csid , (unsigned char*)m_value.u.string); // Always new buffer unless not enough memory
|
|
if (!body)
|
|
n_header = INTL_GetNormalizeStrFromRFC1522(csid , (unsigned char*)stringToMatch); // Always new buffer unless not enough memory
|
|
else
|
|
n_header = INTL_GetNormalizeStr(csid , (unsigned char*)stringToMatch); // Always new buffer unless not enough memory
|
|
|
|
XP_ASSERT(n_str);
|
|
XP_ASSERT(n_header);
|
|
}
|
|
switch (m_operator)
|
|
{
|
|
case opContains:
|
|
if((NULL != n_str) && (NULL != n_header) && (n_str[0]) && INTL_StrContains(csid, n_header, n_str))
|
|
err = SearchError_Success;
|
|
break;
|
|
case opDoesntContain:
|
|
if((NULL != n_str) && (NULL != n_header) && (n_str[0]) && (! INTL_StrContains(csid, n_header, n_str)))
|
|
err = SearchError_Success;
|
|
break;
|
|
case opIs:
|
|
if(n_str && n_header)
|
|
{
|
|
if (n_str[0])
|
|
{
|
|
if (INTL_StrIs(csid, n_header, n_str))
|
|
err = SearchError_Success;
|
|
}
|
|
else if (n_header[0] == '\0') // Special case for "is <the empty string>"
|
|
err = SearchError_Success;
|
|
}
|
|
break;
|
|
case opIsnt:
|
|
if(n_str && n_header)
|
|
{
|
|
if (n_str[0])
|
|
{
|
|
if (! INTL_StrIs(csid, n_header, n_str))
|
|
err = SearchError_Success;
|
|
}
|
|
else if (n_header[0] != '\0') // Special case for "isn't <the empty string>"
|
|
err = SearchError_Success;
|
|
}
|
|
break;
|
|
case opIsEmpty:
|
|
if (!stringToMatch || stringToMatch[0] == '\0')
|
|
err = SearchError_Success;
|
|
break;
|
|
case opBeginsWith:
|
|
if((NULL != n_str) && (NULL != n_header) && INTL_StrBeginWith(csid, n_header, n_str))
|
|
err = SearchError_Success;
|
|
break;
|
|
case opEndsWith:
|
|
{
|
|
if((NULL != n_str) && (NULL != n_header) && INTL_StrEndWith(csid, n_header, n_str))
|
|
err = SearchError_Success;
|
|
}
|
|
break;
|
|
default:
|
|
XP_ASSERT(FALSE);
|
|
}
|
|
|
|
if(n_str) // Need to free the normalized string
|
|
XP_FREE(n_str);
|
|
|
|
if(n_header) // Need to free the normalized string
|
|
XP_FREE(n_header);
|
|
|
|
return err;
|
|
}
|
|
|
|
XP_Bool MSG_SearchTerm::MatchAllBeforeDeciding ()
|
|
{
|
|
if (m_operator == opDoesntContain || m_operator == opIsnt)
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_SearchTerm::MatchRfc822String (const char *string, int16 csid)
|
|
{
|
|
// 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 = NULL, *addresses = NULL;
|
|
|
|
// Change the sense of the loop so we don't bail out prematurely
|
|
// on negative terms. i.e. opDoesntContain must look at all recipients
|
|
MSG_SearchError err;
|
|
MSG_SearchError errContinueLoop;
|
|
if (MatchAllBeforeDeciding())
|
|
err = errContinueLoop = SearchError_Success;
|
|
else
|
|
err = errContinueLoop = SearchError_NotAMatch;
|
|
|
|
int count = MSG_ParseRFC822Addresses (string, &names, &addresses);
|
|
if (count > 0)
|
|
{
|
|
XP_ASSERT(names);
|
|
XP_ASSERT(addresses);
|
|
if (!names || !addresses)
|
|
return err;
|
|
|
|
char *walkNames = names;
|
|
char *walkAddresses = addresses;
|
|
|
|
for (int i = 0; i < count && err == errContinueLoop; i++)
|
|
{
|
|
err = MatchString (walkNames, csid);
|
|
if (errContinueLoop == err)
|
|
err = MatchString (walkAddresses, csid);
|
|
|
|
walkNames += XP_STRLEN(walkNames) + 1;
|
|
walkAddresses += XP_STRLEN(walkAddresses) + 1;
|
|
}
|
|
|
|
XP_FREEIF(names);
|
|
XP_FREEIF(addresses);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_SearchTerm::GetLocalTimes (time_t a, time_t b, struct tm &aTm, struct tm &bTm)
|
|
{
|
|
// Isolate the RTL time weirdnesses here:
|
|
// (1) Must copy the tm since localtime has a static tm
|
|
// (2) localtime can fail if it doesn't like the time_t. Must check the tm* for NULL
|
|
|
|
struct tm *p = localtime(&a);
|
|
if (p)
|
|
{
|
|
XP_MEMCPY (&aTm, p, sizeof(struct tm));
|
|
p = localtime(&b);
|
|
if (p)
|
|
{
|
|
XP_MEMCPY (&bTm, p, sizeof(struct tm));
|
|
return SearchError_Success;
|
|
}
|
|
}
|
|
return SearchError_InvalidAttribute;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_SearchTerm::MatchDate (time_t dateToMatch)
|
|
{
|
|
MSG_SearchError err = SearchError_NotAMatch;
|
|
switch (m_operator)
|
|
{
|
|
case opIsBefore:
|
|
if (dateToMatch < m_value.u.date)
|
|
err = SearchError_Success;
|
|
break;
|
|
case opIsAfter:
|
|
if (dateToMatch > m_value.u.date)
|
|
err = SearchError_Success;
|
|
break;
|
|
case opIs:
|
|
{
|
|
struct tm tmToMatch, tmThis;
|
|
if (SearchError_Success == GetLocalTimes (dateToMatch, m_value.u.date, tmToMatch, tmThis))
|
|
{
|
|
if (tmThis.tm_year == tmToMatch.tm_year &&
|
|
tmThis.tm_mon == tmToMatch.tm_mon &&
|
|
tmThis.tm_mday == tmToMatch.tm_mday)
|
|
err = SearchError_Success;
|
|
}
|
|
}
|
|
break;
|
|
case opIsnt:
|
|
{
|
|
struct tm tmToMatch, tmThis;
|
|
if (SearchError_Success == GetLocalTimes (dateToMatch, m_value.u.date, tmToMatch, tmThis))
|
|
{
|
|
if (tmThis.tm_year != tmToMatch.tm_year ||
|
|
tmThis.tm_mon != tmToMatch.tm_mon ||
|
|
tmThis.tm_mday != tmToMatch.tm_mday)
|
|
err = SearchError_Success;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
XP_ASSERT(FALSE);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_SearchTerm::MatchAge (time_t msgDate)
|
|
{
|
|
MSG_SearchError err = SearchError_NotAMatch;
|
|
time_t now = XP_TIME();
|
|
time_t matchDay = now - (m_value.u.age * 60 * 60 * 24);
|
|
struct tm * matchTime = localtime(&matchDay);
|
|
|
|
// localTime axes previous results so save these.
|
|
int day = matchTime->tm_mday;
|
|
int month = matchTime->tm_mon;
|
|
int year = matchTime->tm_year;
|
|
|
|
struct tm * msgTime = localtime(&msgDate);
|
|
|
|
switch (m_operator)
|
|
{
|
|
case opIsGreaterThan: // is older than
|
|
if (msgDate < matchDay)
|
|
err = SearchError_Success;
|
|
break;
|
|
case opIsLessThan: // is younger than
|
|
if (msgDate > matchDay)
|
|
err = SearchError_Success;
|
|
break;
|
|
case opIs:
|
|
if (matchTime && msgTime)
|
|
if ((day == msgTime->tm_mday)
|
|
&& (month == msgTime->tm_mon)
|
|
&& (year == msgTime->tm_year))
|
|
err = SearchError_Success;
|
|
break;
|
|
default:
|
|
XP_ASSERT(FALSE);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_SearchTerm::MatchSize (uint32 sizeToMatch)
|
|
{
|
|
MSG_SearchError err = SearchError_NotAMatch;
|
|
switch (m_operator)
|
|
{
|
|
case opIsHigherThan:
|
|
if (sizeToMatch > m_value.u.size)
|
|
err = SearchError_Success;
|
|
break;
|
|
case opIsLowerThan:
|
|
if (sizeToMatch < m_value.u.size)
|
|
err = SearchError_Success;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_SearchTerm::MatchStatus (uint32 statusToMatch)
|
|
{
|
|
MSG_SearchError err = SearchError_NotAMatch;
|
|
XP_Bool matches = FALSE;
|
|
|
|
if (statusToMatch & m_value.u.msgStatus)
|
|
matches = TRUE;
|
|
|
|
switch (m_operator)
|
|
{
|
|
case opIs:
|
|
if (matches)
|
|
err = SearchError_Success;
|
|
break;
|
|
case opIsnt:
|
|
if (!matches)
|
|
err = SearchError_Success;
|
|
break;
|
|
default:
|
|
XP_ASSERT(FALSE);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_SearchTerm::MatchPriority (MSG_PRIORITY priorityToMatch)
|
|
{
|
|
MSG_SearchError err = SearchError_NotAMatch;
|
|
|
|
// Use this ittle hack to get around the fact that enums don't have
|
|
// integer compare operators
|
|
int p1 = (int) priorityToMatch;
|
|
int p2 = (int) m_value.u.priority;
|
|
|
|
switch (m_operator)
|
|
{
|
|
case opIsHigherThan:
|
|
if (p1 > p2)
|
|
err = SearchError_Success;
|
|
break;
|
|
case opIsLowerThan:
|
|
if (p1 < p2)
|
|
err = SearchError_Success;
|
|
break;
|
|
case opIs:
|
|
if (p1 == p2)
|
|
err = SearchError_Success;
|
|
break;
|
|
default:
|
|
XP_ASSERT(FALSE);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-------------------- Implementation of MSG_ScopeTerm ------------------------
|
|
//-----------------------------------------------------------------------------
|
|
|
|
MSG_ScopeTerm::MSG_ScopeTerm (MSG_SearchFrame *frame, MSG_ScopeAttribute attrib, MSG_FolderInfo *folder) : msg_OpaqueObject (m_expectedMagic)
|
|
{
|
|
m_attribute = attrib;
|
|
m_folder = folder;
|
|
m_adapter = NULL;
|
|
m_frame = frame;
|
|
m_name = NULL;
|
|
m_server = NULL;
|
|
m_file = 0;
|
|
|
|
m_searchServer = TRUE;
|
|
MSG_LinedPane * pane = m_frame ? m_frame->GetPane() : (MSG_LinedPane*)NULL;
|
|
if (pane)
|
|
m_searchServer = pane->GetPrefs()->GetSearchServer(); // should the scope term be local or on the server?
|
|
|
|
}
|
|
|
|
|
|
MSG_ScopeTerm::MSG_ScopeTerm (MSG_SearchFrame *frame, DIR_Server *server) : msg_OpaqueObject (m_expectedMagic)
|
|
{
|
|
m_server = server;
|
|
DIR_IncrementServerRefCount (m_server);
|
|
m_attribute = scopeLdapDirectory;
|
|
m_adapter = NULL;
|
|
m_frame = frame;
|
|
m_name = NULL;
|
|
m_folder = NULL;
|
|
m_file = NULL;
|
|
|
|
m_searchServer = TRUE;
|
|
MSG_LinedPane * pane = m_frame ? m_frame->GetPane() : (MSG_LinedPane *)NULL;
|
|
if (pane)
|
|
m_searchServer = pane->GetPrefs()->GetSearchServer(); // should the scope term be local or on the server?
|
|
|
|
}
|
|
|
|
|
|
MSG_ScopeTerm::~MSG_ScopeTerm ()
|
|
{
|
|
if (NULL != m_name)
|
|
XP_FREE(m_name);
|
|
if (NULL != m_adapter)
|
|
delete m_adapter;
|
|
if (m_server)
|
|
DIR_DecrementServerRefCount (m_server);
|
|
if (m_file)
|
|
XP_FileClose (m_file);
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_ScopeTerm::InitializeAdapter (MSG_SearchTermArray &termList)
|
|
{
|
|
XP_ASSERT (m_adapter == NULL);
|
|
MSG_SearchError err = SearchError_Success;
|
|
|
|
// mscott: i have added m_searchServer into this switch to take into account the user's preference
|
|
// for searching locally or on the server...
|
|
switch (m_attribute)
|
|
{
|
|
case scopeMailFolder:
|
|
if (m_folder->GetType() == FOLDER_IMAPMAIL && (NET_IsOffline() || !m_searchServer)) // Are we in Offline IMAP mode?
|
|
m_adapter = new msg_SearchIMAPOfflineMail (this, termList);
|
|
else
|
|
if (!IsOfflineMail() && m_searchServer) // Online IMAP && searching the server?
|
|
m_adapter = new msg_SearchOnlineMail (this, termList);
|
|
else
|
|
m_adapter = new msg_SearchOfflineMail (this, termList);
|
|
break;
|
|
case scopeNewsgroup:
|
|
if (NET_IsOffline() || !m_searchServer)
|
|
m_adapter = new msg_SearchOfflineNews (this, termList);
|
|
else
|
|
if (m_folder->KnowsSearchNntpExtension())
|
|
m_adapter = new msg_SearchNewsEx (this, termList);
|
|
else
|
|
m_adapter = new msg_SearchNews (this, termList);
|
|
break;
|
|
case scopeAllSearchableGroups:
|
|
if (!m_searchServer)
|
|
m_adapter = new msg_SearchOfflineNews(this, termList); // mscott: is this the behavior we want?
|
|
else
|
|
m_adapter = new msg_SearchNewsEx (this, termList);
|
|
break;
|
|
case scopeLdapDirectory:
|
|
m_adapter = new msg_SearchLdap (this, termList);
|
|
break;
|
|
case scopeOfflineNewsgroup:
|
|
m_adapter = new msg_SearchOfflineNews (this, termList);
|
|
break;
|
|
default:
|
|
XP_ASSERT(FALSE);
|
|
err = SearchError_InvalidScope;
|
|
}
|
|
|
|
if (m_adapter)
|
|
err = m_adapter->ValidateTerms ();
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
XP_Bool MSG_ScopeTerm::IsOfflineMail ()
|
|
{
|
|
// Find out whether "this" mail folder is online or offline
|
|
XP_ASSERT(m_folder);
|
|
if (m_folder->GetType() == FOLDER_IMAPMAIL && !NET_IsOffline() && m_searchServer) // make sure we are not in offline IMAP (mscott)
|
|
return FALSE;
|
|
return TRUE; // if POP or IMAP in offline mode
|
|
}
|
|
|
|
XP_Bool MSG_ScopeTerm::IsOfflineIMAPMail()
|
|
{
|
|
// Find out whether "this" mail folder is an offline IMAP folder
|
|
XP_ASSERT(m_folder);
|
|
if (m_folder->GetType() == FOLDER_IMAPMAIL && (NET_IsOffline() || !m_searchServer))
|
|
return TRUE;
|
|
return FALSE; // we are not an IMAP folder that is offline
|
|
}
|
|
|
|
|
|
const char *MSG_ScopeTerm::GetMailPath ()
|
|
{
|
|
// if we are POP or OFFLINE IMAP, then we have a Pathname which can be generated (mscott)
|
|
if (IsOfflineMail())
|
|
// if (FOLDER_MAIL == m_folder->GetType())
|
|
return ((MSG_FolderInfoMail*) m_folder)->GetPathname();
|
|
return NULL;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_ScopeTerm::TimeSlice ()
|
|
{
|
|
// For scope terms, time slice means give some processing time to
|
|
// the adapter we own
|
|
return m_adapter->Search();
|
|
}
|
|
|
|
|
|
char *MSG_ScopeTerm::GetStatusBarName ()
|
|
{
|
|
switch (m_attribute)
|
|
{
|
|
case scopeLdapDirectory:
|
|
if (m_server->description)
|
|
return XP_STRDUP(m_server->description);
|
|
else
|
|
return XP_STRDUP(m_server->serverName);
|
|
default:
|
|
{
|
|
char *statusBarName = XP_STRDUP(m_folder->GetName ());
|
|
NET_UnEscape(statusBarName); // Mac apparently stores as "foo%20bar"
|
|
return statusBarName;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// MSG_SearchFrame is a search session. There is one pane object per running
|
|
// customer of search (mail/news, ldap, rules, etc.)
|
|
//-----------------------------------------------------------------------------
|
|
|
|
MSG_SearchFrame::MSG_SearchFrame (MSG_LinedPane *pane) : msg_OpaqueObject (m_expectedMagic)
|
|
{
|
|
// link in the searchFrame with the pane and context
|
|
m_pane = pane;
|
|
MWContext *context = pane->GetContext();
|
|
XP_ASSERT(context);
|
|
context->msg_searchFrame = this;
|
|
|
|
m_sortAttribute = attribSender;
|
|
m_descending = FALSE;
|
|
m_ldapObjectClass = NULL;
|
|
m_idxRunningScope = 0;
|
|
m_parallel = FALSE;
|
|
m_calledStartingUpdate = FALSE;
|
|
m_handlingError = FALSE;
|
|
m_urlStruct = NULL;
|
|
|
|
m_searchType = searchNormal;
|
|
m_pSearchParam = NULL;
|
|
|
|
m_inCylonMode = FALSE;
|
|
m_cylonModeTimer = NULL;
|
|
|
|
m_offlineProgressTotal = 0;
|
|
m_offlineProgressSoFar = 0;
|
|
}
|
|
|
|
|
|
MSG_SearchFrame::~MSG_SearchFrame ()
|
|
{
|
|
DestroyResultList ();
|
|
DestroyScopeList ();
|
|
DestroyTermList ();
|
|
XP_ASSERT(m_viewList.GetSize() == 0); // are all of our views closed? (Search As View related)
|
|
DestroySearchViewList(); // what if we have open views before we kill this?
|
|
|
|
if (m_ldapObjectClass)
|
|
XP_FREE (m_ldapObjectClass);
|
|
|
|
if (m_pSearchParam)
|
|
XP_FREE (m_pSearchParam);
|
|
|
|
if (m_inCylonMode)
|
|
EndCylonMode();
|
|
}
|
|
|
|
|
|
MSG_SearchFrame *MSG_SearchFrame::FromContext (MWContext *context)
|
|
{
|
|
if (!context)
|
|
return NULL;
|
|
MSG_SearchFrame *frame = context->msg_searchFrame;
|
|
if (!frame || !frame->IsValid())
|
|
return NULL;
|
|
return frame;
|
|
}
|
|
|
|
|
|
MSG_SearchFrame *MSG_SearchFrame::FromPane (MSG_Pane *pane)
|
|
{
|
|
if (!pane)
|
|
return NULL;
|
|
return FromContext (pane->GetContext());
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_SearchFrame::Initialize ()
|
|
{
|
|
// Loop over scope terms, initializing an adapter per term. This
|
|
// architecture is necessitated by two things:
|
|
// 1. There might be more than one kind of adapter per if online
|
|
// *and* offline mail mail folders are selected, or if newsgroups
|
|
// belonging to RFC977bis *and* non-RFC977bis are selected
|
|
// 2. Most of the protocols are only capable of searching one scope at a
|
|
// time, so we'll do each scope in a separate adapter on the client
|
|
|
|
MSG_ScopeTerm *scopeTerm = NULL;
|
|
MSG_SearchError err = SearchError_Success;
|
|
|
|
// Ensure that the FE has added scopes and terms to this search
|
|
XP_ASSERT(m_termList.GetSize() > 0);
|
|
if (m_scopeList.GetSize() == 0 || m_termList.GetSize() == 0)
|
|
return SearchError_InvalidPane;
|
|
|
|
// If this term list (loosely specified here by the first term) should be
|
|
// scheduled in parallel, build up a list of scopes to do the round-robin scheduling
|
|
scopeTerm = m_scopeList.GetAt(0);
|
|
if (scopeTerm->m_attribute == scopeLdapDirectory)
|
|
m_parallel = TRUE;
|
|
|
|
for (int i = 0; i < m_scopeList.GetSize() && err == SearchError_Success; i++)
|
|
{
|
|
scopeTerm = m_scopeList.GetAt(i);
|
|
XP_ASSERT(scopeTerm->IsValid());
|
|
|
|
err = scopeTerm->InitializeAdapter (m_termList);
|
|
if (SearchError_Success == err && m_parallel)
|
|
m_parallelScopes.Add (scopeTerm);
|
|
|
|
if (scopeTerm->m_attribute != scopeLdapDirectory && scopeTerm->m_folder->GetType() == FOLDER_MAIL)
|
|
m_offlineProgressTotal += scopeTerm->m_folder->GetTotalMessages();
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_SearchFrame::BeginSearching ()
|
|
{
|
|
MSG_SearchError err = SearchError_Success;
|
|
|
|
// Here's a way to start the URL, but I don't really have time to
|
|
// unify the scheduling mechanisms. If the first scope is a newsgroup, and
|
|
// it's not RFC977bis-capable, we build the URL queue. All other searches can be
|
|
// done with one URL
|
|
|
|
MSG_ScopeTerm *scope = m_scopeList.GetAt(0);
|
|
if (scope->m_attribute == scopeNewsgroup && !scope->m_folder->KnowsSearchNntpExtension() && scope->m_searchServer)
|
|
err = BuildUrlQueue (&msg_SearchNewsEx::PreExitFunction);
|
|
else if (scope->m_attribute == scopeMailFolder && !scope->IsOfflineMail())
|
|
err = BuildUrlQueue (&msg_SearchOnlineMail::PreExitFunction);
|
|
else
|
|
err = GetUrl();
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_SearchFrame::BuildUrlQueue (Net_GetUrlExitFunc *exitFunction)
|
|
{
|
|
MSG_UrlQueue *queue = new MSG_UrlQueue (m_pane);
|
|
|
|
for (int i = 0; i < m_scopeList.GetSize(); i++)
|
|
{
|
|
msg_SearchAdapter *adapter = m_scopeList.GetAt(i)->m_adapter;
|
|
queue->AddUrl (adapter->GetEncoding(), exitFunction);
|
|
}
|
|
|
|
if (queue->GetSize() > 0)
|
|
BeginCylonMode();
|
|
|
|
queue->GetNextUrl();
|
|
|
|
return SearchError_Success;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_SearchFrame::GetUrl ()
|
|
{
|
|
MSG_SearchError err = SearchError_Success;
|
|
if (!m_urlStruct)
|
|
m_urlStruct = NET_CreateURLStruct ("search-libmsg:", NET_DONT_RELOAD);
|
|
|
|
if (m_urlStruct)
|
|
{
|
|
// Set the internal_url flag so just in case someone else happens to have
|
|
// a search-libmsg URL, it won't fire my code, and surely crash.
|
|
m_urlStruct->internal_url = TRUE;
|
|
|
|
// Initiate the asynchronous search
|
|
int getUrlErr = m_pane->GetURL (m_urlStruct, FALSE);
|
|
if (getUrlErr)
|
|
err = (MSG_SearchError) -1; //###phil impedance mismatch
|
|
else
|
|
if (!XP_STRNCMP(m_urlStruct->address, "news:", 5) || !XP_STRNCMP(m_urlStruct->address, "snews:", 6))
|
|
BeginCylonMode();
|
|
}
|
|
else
|
|
err = SearchError_OutOfMemory;
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
void MSG_SearchFrame::DestroySearchViewList()
|
|
{
|
|
for (int i = 0; i < m_viewList.GetSize(); i++)
|
|
delete m_viewList.GetAt(i);
|
|
}
|
|
|
|
void MSG_SearchFrame::DestroyResultList ()
|
|
{
|
|
MSG_ResultElement *result = NULL;
|
|
for (int i = 0; i < m_resultList.GetSize(); i++)
|
|
{
|
|
result = m_resultList.GetAt(i);
|
|
XP_ASSERT (result->IsValid());
|
|
delete result;
|
|
}
|
|
}
|
|
|
|
|
|
void MSG_SearchFrame::DestroyScopeList()
|
|
{
|
|
MSG_ScopeTerm *scope = NULL;
|
|
for (int i = 0; i < m_scopeList.GetSize(); i++)
|
|
{
|
|
scope = m_scopeList.GetAt(i);
|
|
XP_ASSERT (scope->IsValid());
|
|
delete scope;
|
|
}
|
|
}
|
|
|
|
|
|
void MSG_SearchFrame::DestroyTermList ()
|
|
{
|
|
MSG_SearchTerm *term = NULL;
|
|
for (int i = 0; i < m_termList.GetSize(); i++)
|
|
{
|
|
term = m_termList.GetAt(i);
|
|
XP_ASSERT (term->IsValid());
|
|
delete term;
|
|
}
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_SearchFrame::AddSearchTerm (MSG_SearchAttribute attrib, MSG_SearchOperator op, MSG_SearchValue *value, XP_Bool BooleanAND,
|
|
char * arbitraryHeader)
|
|
{
|
|
MSG_SearchTerm *pTerm = new MSG_SearchTerm (attrib, op, value, BooleanAND, arbitraryHeader);
|
|
if (NULL == pTerm)
|
|
return SearchError_OutOfMemory;
|
|
m_termList.Add (pTerm);
|
|
return SearchError_Success;
|
|
}
|
|
|
|
void *MSG_SearchFrame::GetSearchParam()
|
|
{
|
|
return m_pSearchParam;
|
|
}
|
|
|
|
MSG_SearchType MSG_SearchFrame::GetSearchType()
|
|
{
|
|
return m_searchType;
|
|
}
|
|
|
|
MSG_SearchError MSG_SearchFrame::SetSearchParam(MSG_SearchType type, void *param)
|
|
{
|
|
if (m_pSearchParam != NULL && m_pSearchParam != param)
|
|
{
|
|
if (m_searchType == searchLdapVLV)
|
|
{
|
|
XP_FREEIF(((LDAPVirtualList *)m_pSearchParam)->ldvlist_attrvalue);
|
|
XP_FREEIF(((LDAPVirtualList *)m_pSearchParam)->ldvlist_extradata);
|
|
}
|
|
XP_FREE(m_pSearchParam);
|
|
}
|
|
|
|
m_searchType = type;
|
|
m_pSearchParam = param;
|
|
|
|
return SearchError_Success;
|
|
}
|
|
|
|
MSG_SearchError MSG_SearchFrame::AddScopeTerm (MSG_ScopeAttribute attrib, MSG_FolderInfo *folder)
|
|
{
|
|
if (attrib != scopeAllSearchableGroups)
|
|
{
|
|
XP_ASSERT(folder);
|
|
if (!folder)
|
|
return SearchError_NullPointer;
|
|
}
|
|
|
|
MSG_SearchError err = SearchError_Success;
|
|
|
|
if (attrib == scopeMailFolder)
|
|
{
|
|
// It's legal to have a folderInfo which is only a directory, but has no
|
|
// mail folder or summary file. However, such a folderInfo isn't a legal
|
|
// scopeTerm, so turn it away here
|
|
MSG_FolderInfoMail *mailFolder = mailFolder = folder->GetMailFolderInfo();
|
|
XP_StatStruct fileStat;
|
|
if (mailFolder && !XP_Stat(mailFolder->GetPathname(), &fileStat, xpMailFolder) && S_ISDIR(fileStat.st_mode))
|
|
err = SearchError_InvalidFolder;
|
|
|
|
// IMAP folders can have a \NOSELECT flag which means that they can't
|
|
// ever be opened. Since we have to SELECT a folder in order to search
|
|
// it, we'll just omit this folder from the list of search scopes
|
|
MSG_IMAPFolderInfoMail *imapFolder = folder->GetIMAPFolderInfoMail();
|
|
if (imapFolder && !imapFolder->GetCanIOpenThisFolder())
|
|
return SearchError_Success;
|
|
}
|
|
|
|
if ((attrib == scopeNewsgroup || attrib == scopeOfflineNewsgroup) && folder->IsNews())
|
|
{
|
|
// Even unsubscribed newsgroups have a folderInfo, so filter them
|
|
// out here, adding only the newsgroups we are subscribed to
|
|
MSG_FolderInfoNews * newsFolder = (MSG_FolderInfoNews *) folder;
|
|
if (!newsFolder->IsSubscribed())
|
|
return SearchError_Success;
|
|
|
|
// It would be nice if the FEs did this, but I guess no one knows
|
|
// that offline news searching is supposed to work
|
|
if (NET_IsOffline())
|
|
attrib = scopeOfflineNewsgroup;
|
|
}
|
|
|
|
if (attrib == scopeAllSearchableGroups)
|
|
{
|
|
// Try to be flexible about what we get here. It could be a news group,
|
|
// news host, or NULL, which uses the default host.
|
|
if (folder == NULL)
|
|
{
|
|
// I don't know how much of this can be NULL, so I'm not assuming anything
|
|
MSG_NewsHost *host = NULL;
|
|
msg_HostTable *table = m_pane->GetMaster()->GetHostTable();
|
|
if (table)
|
|
{
|
|
host = table->GetDefaultHost(FALSE /*###tw*/);
|
|
if (host)
|
|
folder = host->GetHostInfo();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (folder->GetType())
|
|
{
|
|
case FOLDER_CONTAINERONLY:
|
|
break; // this is what we want -- nothing to do
|
|
case FOLDER_NEWSGROUP:
|
|
case FOLDER_CATEGORYCONTAINER:
|
|
{
|
|
MSG_NewsHost *host = ((MSG_FolderInfoNews*) folder)->GetHost();
|
|
folder = host->GetHostInfo();
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (SearchError_Success == err)
|
|
{
|
|
MSG_ScopeTerm *pScope = new MSG_ScopeTerm (this, attrib, folder);
|
|
if (pScope)
|
|
m_scopeList.Add (pScope);
|
|
else
|
|
err = SearchError_OutOfMemory;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_SearchFrame::AddScopeTerm (DIR_Server *server)
|
|
{
|
|
MSG_ScopeTerm *pScope = new MSG_ScopeTerm (this, server);
|
|
if (NULL == pScope)
|
|
return SearchError_OutOfMemory;
|
|
m_scopeList.Add (pScope);
|
|
return SearchError_Success;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_SearchFrame::SetLdapObjectClass (char *objectClass)
|
|
{
|
|
m_ldapObjectClass = XP_STRDUP(objectClass);
|
|
return SearchError_Success;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_SearchFrame::AddLdapResultsToAddressBook (MSG_ViewIndex *indices, int count)
|
|
{
|
|
MSG_SearchError err = SearchError_Success;
|
|
MSG_UrlQueue *queue = new MSG_AddLdapToAddressBookQueue (m_pane);
|
|
if (queue)
|
|
{
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
MSG_ResultElement *result = m_resultList.GetAt(indices[i]);
|
|
char *url = NULL;
|
|
MSG_SearchValue *dnValue = NULL;
|
|
err = result->GetValue (attribDistinguishedName, &dnValue);
|
|
if (SearchError_Success == err)
|
|
{
|
|
err = ((msg_SearchLdap*)result->m_adapter)->BuildUrl (dnValue->u.string, &url, TRUE /*addToAB*/);
|
|
if (SearchError_Success == err)
|
|
queue->AddUrl (url);
|
|
FREEIF(url);
|
|
MSG_DestroySearchValue (dnValue);
|
|
}
|
|
|
|
}
|
|
queue->GetNextUrl();
|
|
}
|
|
return err;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_SearchFrame::ComposeFromLdapResults (MSG_ViewIndex * indices, int count)
|
|
{
|
|
#define kMailToUrlScheme "mailto:"
|
|
|
|
MSG_SearchError err = SearchError_Success;
|
|
int32 urlLength = XP_STRLEN(kMailToUrlScheme);
|
|
int i;
|
|
|
|
char **addresses = (char**) XP_CALLOC(count, sizeof(char*));
|
|
if (addresses)
|
|
{
|
|
// First build up an array of properly quoted names
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
MSG_ResultElement *elem = m_resultList.GetAt(indices[i]);
|
|
|
|
const MSG_SearchValue *cnValue = elem->GetValueRef (attribCommonName);
|
|
const MSG_SearchValue *mailValue = elem->GetValueRef (attrib822Address);
|
|
if (cnValue && cnValue->u.string && mailValue && mailValue->u.string)
|
|
{
|
|
addresses[i] = MSG_MakeFullAddress (cnValue->u.string, mailValue->u.string);
|
|
if (addresses[i])
|
|
urlLength += XP_STRLEN (addresses[i]) + 1; //+1 for comma
|
|
}
|
|
}
|
|
|
|
// Now catenate them all together
|
|
char *mailtoUrl = (char*) XP_ALLOC (urlLength + 1); // +1 for null term
|
|
if (mailtoUrl)
|
|
{
|
|
XP_STRCPY (mailtoUrl, kMailToUrlScheme);
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
if (addresses[i])
|
|
{
|
|
XP_STRCAT(mailtoUrl, addresses[i]);
|
|
XP_FREE(addresses[i]);
|
|
|
|
// If more than one address, separate with a comma
|
|
if (count > 1 && i < count-1)
|
|
XP_STRCAT(mailtoUrl, ",");
|
|
}
|
|
|
|
}
|
|
|
|
// Send off the mailto URL
|
|
URL_Struct *mailtoStruct = NET_CreateURLStruct (mailtoUrl, NET_DONT_RELOAD);
|
|
if (mailtoStruct)
|
|
m_pane->GetURL (mailtoStruct, FALSE);
|
|
else
|
|
err = SearchError_OutOfMemory;
|
|
|
|
XP_FREE (mailtoUrl);
|
|
}
|
|
else
|
|
err = SearchError_OutOfMemory;
|
|
|
|
XP_FREE(addresses);
|
|
}
|
|
else
|
|
err = SearchError_OutOfMemory;
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_SearchFrame::AddAllScopes (MSG_Master *master, MSG_ScopeAttribute scope)
|
|
{
|
|
MSG_SearchError err = SearchError_Success;
|
|
MSG_FolderInfo *tree = NULL;
|
|
switch (scope)
|
|
{
|
|
case scopeMailFolder:
|
|
tree = master->GetLocalMailFolderTree();
|
|
if (tree)
|
|
err = AddScopeTree (scope, tree);
|
|
else
|
|
{
|
|
XP_ASSERT(FALSE);
|
|
err = SearchError_NullPointer;
|
|
}
|
|
break;
|
|
case scopeNewsgroup:
|
|
{
|
|
msg_HostTable *table = master->GetHostTable();
|
|
for (int i = 0; i < table->getNumHosts(); i++)
|
|
{
|
|
tree = table->getHost(i)->GetHostInfo();
|
|
if (tree)
|
|
AddScopeTree (scope, tree);
|
|
else
|
|
{
|
|
XP_ASSERT(FALSE);
|
|
err = SearchError_NullPointer;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
err = SearchError_InvalidScope;
|
|
}
|
|
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_SearchFrame::AddScopeTree (MSG_ScopeAttribute scope, MSG_FolderInfo *tree, XP_Bool deep)
|
|
{
|
|
MSG_SearchError err = SearchError_Success;
|
|
if (tree->GetDepth() > 1)
|
|
{
|
|
if (scope == scopeMailFolder && tree->GetMailFolderInfo() != NULL)
|
|
{
|
|
err = AddScopeTerm (scope, tree);
|
|
|
|
// It's ok for 'tree' to be not searchable, but we should still
|
|
// look down its subtree for searchable folders
|
|
if (SearchError_InvalidFolder == err)
|
|
err = SearchError_Success;
|
|
}
|
|
else
|
|
if ((scope == scopeNewsgroup || scope == scopeOfflineNewsgroup) && tree->IsNews())
|
|
err = AddScopeTerm (scope, tree);
|
|
|
|
}
|
|
|
|
// okay, we want to skip this part if the user does not want us seaching sub folders / sub categories!
|
|
XP_Bool searchSubFolders = m_pane->GetPrefs()->GetSearchSubFolders();
|
|
|
|
if (SearchError_Success == err && searchSubFolders)
|
|
{
|
|
// Don't recurse the scopes for RFC977bis categories
|
|
MSG_FolderInfoNews *newsTree = tree->GetNewsFolderInfo();
|
|
// okay, FEs do not set the FolderInfo if we are in offline mode...i.e. newsTree
|
|
// will still be listed as Xpat or NNTPExtensions for example. Of course, if we
|
|
// are in Offline mode, we want to recurse each folderInfo!!
|
|
if (newsTree && newsTree->KnowsSearchNntpExtension() && !NET_IsOffline()) // skip if offline mode....
|
|
return err;
|
|
// Don't recurse the scopes for RFC977bis news hosts
|
|
else if (tree->KnowsSearchNntpExtension() && !NET_IsOffline()) // skip if offline mode...
|
|
err = AddScopeTerm (scopeAllSearchableGroups, tree);
|
|
else if (deep)
|
|
{
|
|
// It's a regular old mail folder or non-RFC977bis news host, so we
|
|
// recurse each folderInfo and build a new scope term for it.
|
|
const MSG_FolderArray *subTree = tree->GetSubFolders();
|
|
for (int i = 0; i < subTree->GetSize() && SearchError_Success == err; i++)
|
|
err = AddScopeTree (scope, subTree->GetAt(i));
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_SearchFrame::EncodeRFC977bisScopes (char **ppOutEncoding)
|
|
{
|
|
MSG_SearchError err = SearchError_Success;
|
|
MSG_ScopeTerm *firstScope = m_scopeList.GetAt(0);
|
|
if (firstScope && scopeAllSearchableGroups == firstScope->m_attribute)
|
|
{
|
|
*ppOutEncoding = XP_STRDUP("\"*\""); // NNTP protocol string -- do not localize
|
|
if (!*ppOutEncoding)
|
|
err = SearchError_OutOfMemory;
|
|
}
|
|
else
|
|
{
|
|
int i;
|
|
int cchNames = 0;
|
|
for (i = 0; i < m_scopeList.GetSize(); i++)
|
|
{
|
|
MSG_FolderInfo *folder = m_scopeList.GetAt(i)->m_folder;
|
|
int nameLen = XP_STRLEN (folder->GetName());
|
|
cchNames += nameLen + 1; // Comma between names
|
|
if (FOLDER_CATEGORYCONTAINER == folder->GetType())
|
|
{
|
|
cchNames += nameLen + 3;
|
|
cchNames += 1; // Searching category container "foo" means "foo,foo.*"
|
|
}
|
|
}
|
|
|
|
*ppOutEncoding = (char*) XP_ALLOC(cchNames + 3);// Double-quotes and NULL term
|
|
if (*ppOutEncoding)
|
|
{
|
|
XP_STRCPY (*ppOutEncoding, "\""); // NNTP protocol string -- do not localize
|
|
int count = m_scopeList.GetSize();
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
MSG_FolderInfo *folder = m_scopeList.GetAt(i)->m_folder;
|
|
XP_STRCAT (*ppOutEncoding, folder->GetName());
|
|
if (FOLDER_CATEGORYCONTAINER == folder->GetType())
|
|
{
|
|
XP_STRCAT(*ppOutEncoding, ",");
|
|
XP_STRCAT(*ppOutEncoding, folder->GetName());
|
|
XP_STRCAT(*ppOutEncoding, ".*"); // NNTP protocol string -- do not localize
|
|
}
|
|
if (i + 1 < count) // Some servers (RFC977bis?) don't like a trailing space on the last name
|
|
XP_STRCAT (*ppOutEncoding, ","); // NNTP protocol string -- do not localize
|
|
}
|
|
XP_STRCAT(*ppOutEncoding, "\""); // NNTP protocol string -- do not localize
|
|
}
|
|
else
|
|
err = SearchError_OutOfMemory;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_SearchFrame::GetResultElement (MSG_ViewIndex idx, MSG_ResultElement **result)
|
|
|
|
{
|
|
MSG_SearchError err = SearchError_Success;
|
|
if ((int) idx >= m_resultList.GetSize())
|
|
return SearchError_InvalidResultElement;
|
|
|
|
MSG_ResultElement *res = m_resultList.GetAt(idx);
|
|
if (res && res->IsValid())
|
|
*result = res;
|
|
else
|
|
err = SearchError_InvalidResultElement;
|
|
return err;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_SearchFrame::SortResultList (MSG_SearchAttribute attrib, XP_Bool descending)
|
|
{
|
|
m_pane->StartingUpdate (MSG_NotifyScramble, 0, 0);
|
|
|
|
// Play a little dynamic-scoping trick to get these params into the Compare function
|
|
m_sortAttribute = attrib;
|
|
m_descending = descending;
|
|
m_resultList.QuickSort (MSG_ResultElement::Compare);
|
|
|
|
m_pane->EndingUpdate (MSG_NotifyScramble, 0, 0);
|
|
|
|
return SearchError_Success;
|
|
}
|
|
|
|
|
|
|
|
MSG_SearchError MSG_SearchFrame::AddResultElement (MSG_ResultElement *element)
|
|
{
|
|
XP_ASSERT(element);
|
|
XP_ASSERT(m_pane);
|
|
|
|
XP_Bool bVLV = FALSE;
|
|
MSG_PaneType type = m_pane->GetPaneType();
|
|
XP_ASSERT (MSG_ADDRPANE == type || AB_ABPANE == type || MSG_SEARCHPANE == type || type == AB_PICKERPANE);
|
|
|
|
// Don't tell the FE about VLV results
|
|
if (m_searchType == searchLdapVLV)
|
|
bVLV = TRUE;
|
|
|
|
// Add a new line to the FE's list
|
|
int count = m_resultList.GetSize(), index;
|
|
|
|
if (!bVLV)
|
|
m_pane->StartingUpdate (MSG_NotifyInsertOrDelete, count, 1);
|
|
index = m_resultList.Add (element);
|
|
if (!bVLV)
|
|
m_pane->EndingUpdate (MSG_NotifyInsertOrDelete, count, 1);
|
|
|
|
// Tell the status bar to display new number of hits
|
|
if (!bVLV && m_resultList.GetSize() > 0)
|
|
UpdateStatusBar (MK_MSG_SEARCH_STATUS);
|
|
|
|
return SearchError_Success;
|
|
}
|
|
|
|
void MSG_SearchFrame::AddViewToList(MSG_FolderInfo * folder, MessageDBView * view)
|
|
{
|
|
MSG_SearchView * srchView = new MSG_SearchView;
|
|
if (srchView)
|
|
{
|
|
srchView->folder = folder;
|
|
srchView->view = view;
|
|
m_viewList.Add(srchView);
|
|
}
|
|
}
|
|
|
|
MessageDBView * MSG_SearchFrame::GetView(MSG_FolderInfo * folder)
|
|
{
|
|
MSG_SearchView * srchView;
|
|
// find the first instance of the folder in the search view array
|
|
for (int i = 0; i < m_viewList.GetSize(); i++)
|
|
{
|
|
srchView = m_viewList.GetAt(i);
|
|
if (srchView->folder == folder) // do they point to the same folder?
|
|
return srchView->view;
|
|
}
|
|
return NULL; // not in the list
|
|
}
|
|
|
|
void MSG_SearchFrame::CloseView(MSG_FolderInfo * folder)
|
|
{
|
|
MSG_SearchView * srchView;
|
|
XP_Bool found = FALSE;
|
|
// find the first instance of the folder in the search view array and delete it
|
|
for (int i = 0; i < m_viewList.GetSize() && !found; i++)
|
|
{
|
|
srchView = m_viewList.GetAt(i);
|
|
if (srchView->folder == folder)
|
|
{
|
|
m_viewList.RemoveAt(i);
|
|
srchView->view->Close(); // we opened...so we close it...
|
|
delete srchView;
|
|
found = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
MSG_SearchResultArray * MSG_SearchFrame::IndicesToResultElements (const MSG_ViewIndex *indices, int32 numIndices)
|
|
{
|
|
MSG_SearchResultArray * results = new MSG_SearchResultArray;
|
|
for (int i = 0; i < numIndices; i++)
|
|
{
|
|
MSG_ResultElement * elem = m_resultList.GetAt(indices[i]);
|
|
if (elem)
|
|
results->Add(elem);
|
|
}
|
|
|
|
// now let's sort this new array based on the src folder
|
|
results->QuickSort (MSG_ResultElement::CompareByFolderInfoPtrs);
|
|
return results; // return array...caller must deallocate the memory!
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_SearchFrame::GetLdapObjectClasses (char ** /*array*/, uint16 * /*maxItems*/)
|
|
{
|
|
MSG_SearchError err = SearchError_Success;
|
|
return err;
|
|
}
|
|
|
|
|
|
MsgERR MSG_SearchFrame::MoveMessages (const MSG_ViewIndex *indices,
|
|
int32 numIndices,
|
|
MSG_FolderInfo * destFolder)
|
|
{
|
|
return ProcessSearchAsViewCmd(indices, numIndices, destFolder, MSG_SearchAsViewMove);
|
|
}
|
|
|
|
MsgERR MSG_SearchFrame::CopyMessages (const MSG_ViewIndex *indices,
|
|
int32 numIndices,
|
|
MSG_FolderInfo * destFolder,
|
|
XP_Bool deleteAfterCopy)
|
|
{
|
|
if (deleteAfterCopy)
|
|
return ProcessSearchAsViewCmd(indices, numIndices, destFolder, MSG_SearchAsViewMove);
|
|
else
|
|
return ProcessSearchAsViewCmd(indices, numIndices, destFolder, MSG_SearchAsViewCopy);
|
|
}
|
|
|
|
MsgERR MSG_SearchFrame::TrashMessages (const MSG_ViewIndex * indices,
|
|
int32 numIndices)
|
|
{
|
|
return ProcessSearchAsViewCmd(indices, numIndices, NULL, MSG_SearchAsViewDelete);
|
|
}
|
|
|
|
MsgERR MSG_SearchFrame::DeleteMessages (const MSG_ViewIndex * indices,
|
|
int32 numIndices)
|
|
{
|
|
return ProcessSearchAsViewCmd(indices, numIndices, NULL, MSG_SearchAsViewDeleteNoTrash);
|
|
}
|
|
|
|
MSG_DragEffect MSG_SearchFrame::DragMessagesStatus(const MSG_ViewIndex *indices, int32 numIndices,MSG_FolderInfo * destFolder, MSG_DragEffect request)
|
|
{
|
|
if (!destFolder)
|
|
return MSG_Drag_Not_Allowed;
|
|
|
|
// how to handle source folders for search as view where they can all be different. Fortunately, the source folders should be either
|
|
// all IMAP folders, all POP, or all News since we cannot currently support searching over different servers. If this fact changes, then
|
|
// this step is going to be complicated.
|
|
|
|
MSG_SearchValue * value;
|
|
MSG_ResultElement * elem = NULL;;
|
|
|
|
// If there are indices, then we have a source folder. Otherwise, just check the destination
|
|
// folder.
|
|
if (numIndices > 0)
|
|
elem = m_resultList.GetAt(indices[0]); // we don't care which source folder we get.
|
|
if ((numIndices > 0) ? (elem != NULL) : TRUE)
|
|
{
|
|
// Which destinations can have messages added to them?
|
|
|
|
if (destFolder->GetDepth() <= 1) // root of tree or server.
|
|
return MSG_Drag_Not_Allowed; // (needed because local mail server has type "FOLDER_MAIL").
|
|
|
|
FolderType destType = destFolder->GetType();
|
|
|
|
if (destType == FOLDER_CONTAINERONLY // can't drag a message to a server
|
|
|| destType == FOLDER_CATEGORYCONTAINER
|
|
|| destType == FOLDER_IMAPSERVERCONTAINER
|
|
|| destType == FOLDER_NEWSGROUP) // should we offer to post the message? - jrm
|
|
return MSG_Drag_Not_Allowed;
|
|
|
|
MSG_IMAPFolderInfoMail *imapDest = destFolder->GetIMAPFolderInfoMail();
|
|
if (imapDest)
|
|
{
|
|
if (!imapDest->GetCanDropMessagesIntoFolder())
|
|
return MSG_Drag_Not_Allowed;
|
|
}
|
|
|
|
if (numIndices <= 0 || !elem)
|
|
{
|
|
// This is as far as we can go; there is no source folder, so the FE
|
|
// only wants to find out if drops are allowed in the destination.
|
|
return request;
|
|
}
|
|
|
|
elem->GetValue(attribFolderInfo, & value);
|
|
MSG_FolderInfo *srcFolder = value->u.folder;
|
|
if (srcFolder)
|
|
{
|
|
FolderType sourceType = srcFolder->GetType();
|
|
|
|
// Which drags are required to be copies?
|
|
XP_Bool mustCopy = FALSE;
|
|
if ((sourceType == FOLDER_MAIL) != (destType == FOLDER_MAIL)) // Bug #55177 drag between local and server...
|
|
mustCopy = TRUE;
|
|
else if (sourceType == FOLDER_NEWSGROUP)
|
|
mustCopy = TRUE;
|
|
else if (sourceType == FOLDER_IMAPMAIL) // if IMAP, we actually have to go through each source folder and check ACLs
|
|
{
|
|
if (numIndices < 50) // we probably don't want to check the ACL on too many folders, just for drag/drop status
|
|
{
|
|
int k = 0;
|
|
while (!mustCopy && (k < numIndices)) // drop out once we hit the first one that fails
|
|
{
|
|
MSG_ResultElement *imapElem = m_resultList.GetAt(indices[k]);
|
|
if (imapElem)
|
|
{
|
|
elem->GetValue(attribFolderInfo, & value);
|
|
MSG_FolderInfo *folderK = value->u.folder;
|
|
if (folderK)
|
|
{
|
|
MSG_IMAPFolderInfoMail *imapFolderK = folderK->GetIMAPFolderInfoMail();
|
|
if (imapFolderK && !imapFolderK->GetCanDeleteMessagesInFolder())
|
|
mustCopy = TRUE;
|
|
}
|
|
}
|
|
k++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mustCopy)
|
|
return (MSG_DragEffect)(request & MSG_Require_Copy);
|
|
|
|
// Now, if they don't care, give them a move
|
|
if ((request & MSG_Require_Move) == MSG_Require_Move)
|
|
return MSG_Require_Move;
|
|
|
|
// Otherwise, give them what they want
|
|
return request;
|
|
}
|
|
}
|
|
return MSG_Drag_Not_Allowed;
|
|
}
|
|
|
|
|
|
|
|
MsgERR MSG_SearchFrame::ProcessSearchAsViewCmd (const MSG_ViewIndex * indices, int32 numIndices,
|
|
MSG_FolderInfo * destFolder, /* might be NULL if cmd is delete */
|
|
SearchAsViewCmd cmdType)
|
|
// indices contains search result elements from different folders!! Our job is to break it down into
|
|
// folders and message keys for each folder. Then perform the SearchAsViewCmd on each folder and its associated
|
|
// set of message keys. Some of the arguments passed into this routine are ignored depending on the
|
|
// search as view command being performed.
|
|
|
|
{
|
|
|
|
MSG_ResultElement * elem;
|
|
MSG_SearchValue * value;
|
|
MSG_FolderInfo * srcFolder = NULL;
|
|
|
|
if (numIndices == 0)
|
|
return eSUCCESS;
|
|
|
|
MSG_SearchResultArray * effectedResults = IndicesToResultElements(indices, numIndices); // also sorts by folderInfo
|
|
if (!effectedResults)
|
|
return eOUT_OF_MEMORY; // should this be a different error condition?
|
|
|
|
int index = 0;
|
|
|
|
// repeat until we have looked at every search result element
|
|
while (index < effectedResults->GetSize())
|
|
{
|
|
// each new iteration through, means we are looking at a new folder...
|
|
elem = effectedResults->GetAt(index);
|
|
if (elem)
|
|
{
|
|
IDArray * ids = new IDArray; // create a new ID Array
|
|
elem->GetValue(attribFolderInfo, &value);
|
|
srcFolder = value->u.folder; // get the src folder
|
|
|
|
if (!srcFolder)
|
|
break;
|
|
|
|
elem->GetValue(attribMessageKey, &value); // add our first element
|
|
ids->Add(value->u.key);
|
|
|
|
index++; // This ensures that we never have an infinite loop in the outer while loop!
|
|
|
|
XP_Bool sameFolder = TRUE;
|
|
// add the message IDs for all messages also in this same folder
|
|
while (index < effectedResults->GetSize() && sameFolder)
|
|
{
|
|
elem = effectedResults->GetAt(index);
|
|
elem->GetValue(attribFolderInfo, &value);
|
|
if (value->u.folder == srcFolder)
|
|
{
|
|
elem->GetValue(attribMessageKey, &value);
|
|
ids->Add(value->u.key);
|
|
index++;
|
|
}
|
|
else
|
|
sameFolder = FALSE;
|
|
}
|
|
|
|
// small hack.. If the source folder is a Newsgroup, then we can only copy it!
|
|
// so change the FE command if they were trying a move. This simplifies FE work since they can always
|
|
// call MOVE without ever having to check if a source folder is news group.
|
|
MsgERR status = eSUCCESS;
|
|
|
|
if ((srcFolder->GetType() == FOLDER_NEWSGROUP) && (cmdType == MSG_SearchAsViewMove))
|
|
cmdType = MSG_SearchAsViewCopy;
|
|
|
|
// if the srcFolder is a news group,
|
|
if (srcFolder->IsNews() && (cmdType == MSG_SearchAsViewDelete || cmdType == MSG_SearchAsViewDeleteNoTrash))
|
|
status = eFAILURE;
|
|
|
|
// All the message IDs for this folder have been added, so perform the command...
|
|
if (status == eSUCCESS)
|
|
{
|
|
switch (cmdType)
|
|
{
|
|
case MSG_SearchAsViewMove:
|
|
CopyResultElements(srcFolder, destFolder, ids, TRUE /* delete after copy */);
|
|
break;
|
|
case MSG_SearchAsViewCopy:
|
|
CopyResultElements(srcFolder, destFolder, ids, FALSE /* do not delete after copy */);
|
|
break;
|
|
case MSG_SearchAsViewDelete:
|
|
TrashResultElements(srcFolder, ids);
|
|
break;
|
|
case MSG_SearchAsViewDeleteNoTrash:
|
|
DeleteResultElements(srcFolder, ids);
|
|
break;
|
|
default:
|
|
XP_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
index++; // if we can do nothing else...
|
|
}
|
|
|
|
delete effectedResults;
|
|
|
|
for (int i = m_resultList.GetSize() - 1; i >= 0; i--)
|
|
{
|
|
elem = m_resultList.GetAt(i);
|
|
// for each result element, determine if its index is in indices...
|
|
for (int k = 0; k < numIndices; k++)
|
|
if (indices[k] == i)
|
|
{
|
|
numIndices--;
|
|
m_pane->StartingUpdate(MSG_NotifyInsertOrDelete, i, -1);
|
|
m_resultList.RemoveAt(i);
|
|
if (elem)
|
|
{
|
|
delete elem;
|
|
elem = NULL;
|
|
}
|
|
m_pane->EndingUpdate(MSG_NotifyInsertOrDelete, i, -1);
|
|
}
|
|
}
|
|
|
|
return eSUCCESS;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_SearchFrame::CopyResultElements (MSG_FolderInfo * srcFolder, MSG_FolderInfo * destFolder,
|
|
IDArray * ids, XP_Bool deleteAfterCopy)
|
|
{
|
|
if((srcFolder == NULL) || (destFolder == NULL))
|
|
return SearchError_NullPointer;
|
|
|
|
MessageDBView * view = NULL;
|
|
// now all the message keys have been added...so perform this copy
|
|
char * url = srcFolder->BuildUrl(NULL, MSG_MESSAGEKEYNONE);
|
|
MessageDBView::OpenURL(url, m_pane->GetMaster(), ViewAny, & view, FALSE);
|
|
AddViewToList(srcFolder, view); // register the view in our search frame so it can close it when copy finished.
|
|
srcFolder->StartAsyncCopyMessagesInto (destFolder, m_pane, view->GetDB(), ids, ids->GetSize(),
|
|
m_pane->GetContext(), NULL, deleteAfterCopy, MSG_MESSAGEKEYNONE);
|
|
return SearchError_Success;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_SearchFrame::TrashResultElements (MSG_FolderInfo * srcFolder, IDArray * ids)
|
|
{
|
|
XP_Bool imapDeleteIsMoveToTrash = (srcFolder) ? srcFolder->DeleteIsMoveToTrash() : FALSE;
|
|
|
|
// first set up a folder Info for the trash.
|
|
char * path = NULL;
|
|
MSG_FolderInfoMail * trashFolder = NULL;
|
|
|
|
// make sure we are are NOT a Trash folder OR an IMAP folder where delete is NOT move to trash...
|
|
if (srcFolder && !(srcFolder->GetFlags() & MSG_FOLDER_FLAG_TRASH) &&
|
|
!((srcFolder->GetType() == FOLDER_IMAPMAIL) && !imapDeleteIsMoveToTrash))
|
|
{
|
|
// determine if folder is a local mail folder...use local trash
|
|
if (srcFolder->GetType() == FOLDER_MAIL)
|
|
{
|
|
path = m_pane->GetPrefs()->MagicFolderName (MSG_FOLDER_FLAG_TRASH);
|
|
trashFolder = m_pane->GetMaster()->FindMailFolder(path, TRUE);
|
|
}
|
|
else // must be an IMAP folder...
|
|
{
|
|
// IMAP trash folder...
|
|
MSG_IMAPFolderInfoMail *imapInfo = srcFolder->GetIMAPFolderInfoMail();
|
|
if (imapInfo)
|
|
{
|
|
MSG_FolderInfo *foundTrash = MSG_GetTrashFolderForHost(imapInfo->GetIMAPHost());
|
|
trashFolder = foundTrash ? foundTrash->GetMailFolderInfo() :
|
|
(MSG_FolderInfoMail *)NULL;
|
|
}
|
|
}
|
|
|
|
XP_FREEIF(path);
|
|
MSG_SearchError err = CopyResultElements (srcFolder, trashFolder, ids, TRUE);
|
|
return err;
|
|
|
|
}
|
|
else
|
|
DeleteResultElements(srcFolder, ids);
|
|
|
|
return SearchError_Success;
|
|
}
|
|
|
|
MSG_SearchError MSG_SearchFrame::DeleteResultElements(MSG_FolderInfo * srcFolder, IDArray * ids)
|
|
// actually delete (not move to trash) messages with the appropriate indices from the srcFolder...
|
|
{
|
|
MessageDBView * view = NULL;
|
|
|
|
if (srcFolder)
|
|
{
|
|
if (srcFolder->GetType () == FOLDER_MAIL)
|
|
{
|
|
char * url = srcFolder->BuildUrl(NULL, MSG_MESSAGEKEYNONE);
|
|
MessageDBView::OpenURL(url, m_pane->GetMaster(), ViewAny, & view, FALSE);
|
|
if (view)
|
|
{
|
|
view->GetDB()->DeleteMessages(*ids, NULL); // delete the message from the data base....
|
|
AddViewToList(srcFolder, view); // register the view in our search frame so it can close it when copy finished.
|
|
}
|
|
|
|
}
|
|
else if (srcFolder->GetType() == FOLDER_IMAPMAIL)
|
|
((MSG_IMAPFolderInfoMail *) srcFolder)->DeleteSpecifiedMessages(m_pane, *ids);
|
|
}
|
|
return SearchError_Success;
|
|
}
|
|
|
|
|
|
MSG_SearchError MSG_SearchFrame::DeleteResultElementsInFolder (MSG_FolderInfo *folder)
|
|
{
|
|
// When the user deletes a mail folder or newsgroup while we have search
|
|
// results open on it, we should remove any such results from the view.
|
|
// Otherwise we have stale folderInfo pointers hanging around.
|
|
|
|
if (!folder)
|
|
return SearchError_NullPointer;
|
|
|
|
for (int i = m_resultList.GetSize() - 1; i >= 0; i--)
|
|
{
|
|
MSG_ResultElement *elem = m_resultList.GetAt(i);
|
|
if (elem->m_adapter->m_scope->m_folder == folder)
|
|
{
|
|
m_pane->StartingUpdate (MSG_NotifyInsertOrDelete, i, -1);
|
|
m_resultList.RemoveAt (i);
|
|
delete elem;
|
|
m_pane->EndingUpdate (MSG_NotifyInsertOrDelete, i, -1);
|
|
}
|
|
}
|
|
|
|
return SearchError_Success;
|
|
}
|
|
|
|
|
|
int MSG_SearchFrame::TimeSlice ()
|
|
{
|
|
MSG_ScopeTerm *scope = GetRunningScope();
|
|
if (scope && scope->m_adapter->m_abortCalled)
|
|
return -1;
|
|
|
|
// Decide whether each scope in this search should get time in parallel, or whether
|
|
// each scope should finish before the next one starts
|
|
return m_parallel ? TimeSliceParallel() : TimeSliceSerial();
|
|
}
|
|
|
|
|
|
int MSG_SearchFrame::TimeSliceSerial ()
|
|
{
|
|
// This version of TimeSlice runs each scope term one at a time, and waits until one
|
|
// scope term is finished before starting another one. When we're searching the local
|
|
// disk, this is the fastest way to do it.
|
|
|
|
MSG_ScopeTerm *scope = GetRunningScope();
|
|
if (scope)
|
|
{
|
|
MSG_SearchError err = scope->TimeSlice ();
|
|
if (err == SearchError_ScopeDone)
|
|
{
|
|
m_idxRunningScope++;
|
|
if (m_idxRunningScope < m_scopeList.GetSize())
|
|
UpdateStatusBar (MK_MSG_SEARCH_STATUS);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
FE_SetProgressBarPercent (GetContext(), 0);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
|
|
int MSG_SearchFrame::TimeSliceParallel ()
|
|
{
|
|
// This version of TimeSlice runs all scope terms at once, not waiting for any
|
|
// to complete. It stops when all scope terms are done. When we're searching
|
|
// a remote server, this is the fastest way to do it.
|
|
|
|
MSG_SearchError err = SearchError_Success;
|
|
int count = m_parallelScopes.GetSize();
|
|
if (count > 0)
|
|
{
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
MSG_ScopeTerm *scope = m_parallelScopes.GetAt (i);
|
|
err = scope->TimeSlice();
|
|
|
|
// This will remove this scope from the round-robin in the
|
|
// event of an error, or of normal completion (ScopeDone)
|
|
if (SearchError_Success != err)
|
|
m_parallelScopes.RemoveAt(i);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
|
|
void MSG_SearchFrame::UpdateStatusBar (int message)
|
|
{
|
|
// Fudge factor for a %d in a printf format string
|
|
const int kPercentDFudge = 11;
|
|
|
|
char *hitsString = NULL;
|
|
int hitsStringLength = 0;
|
|
|
|
if (message == MK_MSG_SEARCH_STATUS && m_resultList.GetSize() > 0)
|
|
{
|
|
// Build the "Found 32 matches" string
|
|
char *hitsTemplate = XP_GetString (MK_SEARCH_HITS_COUNTER);
|
|
hitsStringLength = XP_STRLEN(hitsTemplate) + kPercentDFudge;
|
|
hitsString = new char [hitsStringLength];
|
|
if (hitsString)
|
|
PR_snprintf (hitsString, hitsStringLength, hitsTemplate, m_resultList.GetSize());
|
|
}
|
|
|
|
// Build the progress string based on "message". If we built a hitsString, use it
|
|
MSG_ScopeTerm *scope = GetRunningScope ();
|
|
char *searchTemplate = XP_GetString (message);
|
|
char *scopeName = scope->GetStatusBarName ();
|
|
int statusStringLength = XP_STRLEN(searchTemplate) + XP_STRLEN(scopeName) + kPercentDFudge;
|
|
if (hitsString)
|
|
statusStringLength += hitsStringLength;
|
|
char *statusString = new char [statusStringLength];
|
|
if (statusString)
|
|
{
|
|
PR_snprintf (statusString, statusStringLength, searchTemplate, scopeName);
|
|
if (hitsString)
|
|
XP_STRNCAT_SAFE (statusString, hitsString, statusStringLength - XP_STRLEN(statusString));
|
|
FE_Progress (GetContext(), statusString);
|
|
delete [] statusString;
|
|
}
|
|
|
|
if (hitsString)
|
|
delete [] hitsString;
|
|
|
|
XP_FREEIF(scopeName);
|
|
}
|
|
|
|
|
|
msg_SearchAdapter *MSG_SearchFrame::GetRunningAdapter ()
|
|
{
|
|
MSG_ScopeTerm *scope = GetRunningScope();
|
|
if (scope)
|
|
return GetRunningScope()->m_adapter;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
MSG_ScopeTerm *MSG_SearchFrame::GetRunningScope ()
|
|
{
|
|
if (m_idxRunningScope < m_scopeList.GetSize())
|
|
return m_scopeList.GetAt (m_idxRunningScope);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
const char *MSG_SearchFrame::GetLdapObjectClass ()
|
|
{
|
|
// If the caller has set an object class, we should use it. Otherwise
|
|
// the DWIM setting is to search for people.
|
|
return m_ldapObjectClass ? m_ldapObjectClass : "Person";
|
|
}
|
|
|
|
|
|
XP_Bool MSG_SearchFrame::GetGoToFolderStatus (MSG_ViewIndex *indices, int32 numIndices)
|
|
{
|
|
for (int32 i = 0; i < numIndices; i++)
|
|
{
|
|
MSG_ResultElement *result = m_resultList.GetAt(indices[i]);
|
|
if (!result)
|
|
return FALSE;
|
|
|
|
MSG_SearchValue *folderValue = NULL;
|
|
if (SearchError_Success == result->GetValue (attribFolderInfo, &folderValue))
|
|
{
|
|
if (folderValue)
|
|
{
|
|
if (!folderValue->u.folder)
|
|
{
|
|
delete folderValue;
|
|
return FALSE;
|
|
}
|
|
delete folderValue;
|
|
}
|
|
else
|
|
return FALSE;
|
|
}
|
|
else
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
XP_Bool MSG_SearchFrame::GetSaveProfileStatus ()
|
|
{
|
|
if (m_scopeList.GetSize() > 0 && m_resultList.GetSize() > 0)
|
|
{
|
|
// Figure out whether the news host for the selected folder is capable of virtual groups
|
|
MSG_FolderInfo *folder = m_scopeList.GetAt(0)->m_folder;
|
|
MSG_NewsHost *host = NULL;
|
|
MSG_FolderInfoNews *newsFolder = folder->GetNewsFolderInfo();
|
|
|
|
if (newsFolder)
|
|
host = newsFolder->GetHost();
|
|
else if (FOLDER_CONTAINERONLY == folder->GetType())
|
|
host = ((MSG_NewsFolderInfoContainer*) folder)->GetHost();
|
|
if (host)
|
|
return host->QueryExtension("PROFILE");
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
msg_SearchNewsEx *MSG_SearchFrame::GetProfileAdapter()
|
|
{
|
|
if (GetSaveProfileStatus())
|
|
return (msg_SearchNewsEx*) m_scopeList.GetAt(0)->m_adapter;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void MSG_SearchFrame::BeginCylonMode ()
|
|
{
|
|
if (m_cylonModeTimer)
|
|
{
|
|
FE_ClearTimeout (m_cylonModeTimer);
|
|
m_cylonModeTimer = NULL;
|
|
}
|
|
|
|
m_cylonModeTimer = FE_SetTimeout ((TimeoutCallbackFunction)MSG_SearchFrame::CylonModeCallback, this, 333 /*0.333 sec*/);
|
|
if (m_cylonModeTimer)
|
|
m_inCylonMode = TRUE;
|
|
}
|
|
|
|
|
|
void MSG_SearchFrame::EndCylonMode ()
|
|
{
|
|
m_inCylonMode = FALSE;
|
|
if (m_cylonModeTimer)
|
|
{
|
|
FE_ClearTimeout (m_cylonModeTimer);
|
|
m_cylonModeTimer = NULL;
|
|
}
|
|
FE_SetProgressBarPercent (GetContext(), 0);
|
|
}
|
|
|
|
|
|
void MSG_SearchFrame::CylonModeCallback (void *closure)
|
|
{
|
|
MSG_SearchFrame *frame = (MSG_SearchFrame*) closure;
|
|
|
|
if (frame->m_inCylonMode)
|
|
{
|
|
FE_SetProgressBarPercent (frame->GetContext(), -1);
|
|
frame->m_cylonModeTimer = NULL;
|
|
frame->BeginCylonMode ();
|
|
}
|
|
}
|
|
|
|
|
|
void MSG_SearchFrame::IncrementOfflineProgress ()
|
|
{
|
|
// Increment the thermometer in the search dialog
|
|
int32 percent = (int32) (100 * (((double) ++m_offlineProgressSoFar) / ((double) m_offlineProgressTotal)));
|
|
FE_SetProgressBarPercent (GetContext(), percent);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//------------------- Validity checking for menu items etc. -------------------
|
|
//-----------------------------------------------------------------------------
|
|
|
|
msg_SearchValidityTable::msg_SearchValidityTable ()
|
|
{
|
|
// Set everything to be unavailable and disabled
|
|
for (int i = 0; i < kNumAttributes; i++)
|
|
for (int j = 0; j < kNumOperators; j++)
|
|
{
|
|
SetAvailable (i, j, FALSE);
|
|
SetEnabled (i, j, FALSE);
|
|
SetValidButNotShown (i,j, FALSE);
|
|
}
|
|
m_numAvailAttribs = 0; // # of attributes marked with at least one available operator
|
|
}
|
|
|
|
int msg_SearchValidityTable::GetNumAvailAttribs()
|
|
{
|
|
m_numAvailAttribs = 0;
|
|
for (int i = 0; i < kNumAttributes; i++)
|
|
for (int j = 0; j < kNumOperators; j++)
|
|
if (GetAvailable(i, j))
|
|
{
|
|
m_numAvailAttribs++;
|
|
break;
|
|
}
|
|
return m_numAvailAttribs;
|
|
}
|
|
|
|
MSG_SearchError msg_SearchValidityTable::ValidateTerms (MSG_SearchTermArray &termList)
|
|
{
|
|
MSG_SearchError err = SearchError_Success;
|
|
|
|
for (int i = 0; i < termList.GetSize(); i++)
|
|
{
|
|
MSG_SearchTerm *term = termList.GetAt(i);
|
|
XP_ASSERT(term->IsValid());
|
|
if (!GetEnabled(term->m_attribute, term->m_operator) ||
|
|
!GetAvailable(term->m_attribute, term->m_operator))
|
|
{
|
|
if (!GetValidButNotShown(term->m_attribute, term->m_operator))
|
|
err = SearchError_ScopeAgreement;
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
// global variable with destructor allows automatic cleanup
|
|
msg_SearchValidityManager gValidityMgr;
|
|
|
|
|
|
msg_SearchValidityManager::msg_SearchValidityManager ()
|
|
{
|
|
m_offlineMailTable = NULL;
|
|
m_onlineMailTable = NULL;
|
|
m_onlineMailFilterTable = NULL;
|
|
m_newsTable = NULL;
|
|
m_localNewsTable = NULL;
|
|
m_newsExTable = NULL;
|
|
m_ldapTable = NULL;
|
|
}
|
|
|
|
|
|
msg_SearchValidityManager::~msg_SearchValidityManager ()
|
|
{
|
|
if (NULL != m_offlineMailTable)
|
|
delete m_offlineMailTable;
|
|
if (NULL != m_onlineMailTable)
|
|
delete m_onlineMailTable;
|
|
if (NULL != m_onlineMailFilterTable)
|
|
delete m_onlineMailFilterTable;
|
|
if (NULL != m_newsTable)
|
|
delete m_newsTable;
|
|
if (NULL != m_localNewsTable)
|
|
delete m_localNewsTable;
|
|
if (NULL != m_newsExTable)
|
|
delete m_newsExTable;
|
|
if (NULL != m_ldapTable)
|
|
delete m_ldapTable;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Bottleneck accesses to the objects so we can allocate and initialize them
|
|
// lazily. This way, there's no heap overhead for the validity tables until the
|
|
// user actually searches that scope.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
MSG_SearchError msg_SearchValidityManager::GetTable (int whichTable, msg_SearchValidityTable **ppOutTable)
|
|
{
|
|
if (NULL == ppOutTable)
|
|
return SearchError_NullPointer;
|
|
|
|
MSG_SearchError err = SearchError_Success;
|
|
|
|
// hack...currently FEs are setting scope to News even if it should be set to OfflineNewsgroups...
|
|
// i'm fixing this by checking if we are in offline mode...
|
|
if (NET_IsOffline() && (whichTable == news || whichTable == newsEx))
|
|
whichTable = localNews;
|
|
|
|
|
|
switch (whichTable)
|
|
{
|
|
case offlineMail:
|
|
if (!m_offlineMailTable)
|
|
err = InitOfflineMailTable ();
|
|
*ppOutTable = m_offlineMailTable;
|
|
break;
|
|
case onlineMail:
|
|
if (!m_onlineMailTable)
|
|
err = InitOnlineMailTable ();
|
|
*ppOutTable = m_onlineMailTable;
|
|
break;
|
|
case onlineMailFilter:
|
|
if (!m_onlineMailFilterTable)
|
|
err = InitOnlineMailFilterTable ();
|
|
*ppOutTable = m_onlineMailFilterTable;
|
|
break;
|
|
case news:
|
|
if (!m_newsTable)
|
|
err = InitNewsTable ();
|
|
*ppOutTable = m_newsTable;
|
|
break;
|
|
case localNews:
|
|
if (!m_localNewsTable)
|
|
err = InitLocalNewsTable();
|
|
*ppOutTable = m_localNewsTable;
|
|
break;
|
|
case newsEx:
|
|
if (!m_newsExTable)
|
|
err = InitNewsExTable ();
|
|
*ppOutTable = m_newsExTable;
|
|
break;
|
|
case Ldap:
|
|
if (!m_ldapTable)
|
|
err = InitLdapTable ();
|
|
*ppOutTable = m_ldapTable;
|
|
break;
|
|
default:
|
|
XP_ASSERT(0);
|
|
err = SearchError_NotAMatch;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
MSG_SearchError msg_SearchValidityManager::NewTable (msg_SearchValidityTable **ppOutTable)
|
|
{
|
|
XP_ASSERT (ppOutTable);
|
|
*ppOutTable = new msg_SearchValidityTable;
|
|
if (NULL == *ppOutTable)
|
|
return SearchError_OutOfMemory;
|
|
|
|
return SearchError_Success;
|
|
}
|
|
|