/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Robert John Churchill      <rjc@netscape.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

/*
  Implementation for an internet search RDF data store.
 */

#include "nsInternetSearchService.h"
#include "nsLocalSearchService.h"

#include "nscore.h"
#include "nsIEnumerator.h"
#include "nsIRDFObserver.h"
#include "nsIRDFContainer.h"
#include "nsIRDFContainerUtils.h"
#include "nsIServiceManager.h"
#include "nsVoidArray.h"  // XXX introduces dependency on raptorbase
#include "nsXPIDLString.h"
#include "nsRDFCID.h"
#include "plhash.h"
#include "plstr.h"
#include "prmem.h"
#include "prprf.h"
#include "prio.h"
#include "prlog.h"
#include "rdf.h"
#include "nsIDirectoryService.h"
#include "nsDirectoryServiceDefs.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsCOMArray.h"
#include "nsCRT.h"
#include "nsEnumeratorUtils.h"
#include "nsIRDFRemoteDataSource.h"
#include "nsICharsetConverterManager.h"
#include "nsICharsetAlias.h"
#include "nsITextToSubURI.h"
#include "nsEscape.h"
#include "nsNetUtil.h"
#include "nsIChannel.h"
#include "nsIFileChannel.h"
#include "nsIHttpChannel.h"
#include "nsIUploadChannel.h"
#include "nsIInputStream.h"
#ifndef MOZ_PLACES
#include "nsIBookmarksService.h"
#endif
#include "nsIStringBundle.h"
#include "nsIObserverService.h"
#include "nsIURL.h"
#include "nsILocalFile.h"
#include "nsUnicharUtils.h"
#include "nsReadableUtils.h"
#include "nsIPrefLocalizedString.h"
#include "nsIGenericFactory.h"

#ifdef	XP_WIN
#include "windef.h"
#include "winbase.h"
#endif

#ifdef	DEBUG
// #define	DEBUG_SEARCH_OUTPUT	1
// #define	DEBUG_SEARCH_UPDATES	1
#endif

#define MAX_SEARCH_RESULTS_ALLOWED  100

#define POSTHEADER_PREFIX "Content-type: application/x-www-form-urlencoded\r\nContent-Length: "
#define POSTHEADER_SUFFIX "\r\n\r\n"
#define SEARCH_PROPERTIES "chrome://communicator/locale/search/search-panel.properties"
#ifdef MOZ_XUL_APP
#define SEARCHCONFIG_PROPERTIES "chrome://branding/content/searchconfig.properties"
#define INTL_PROPERTIES "chrome://global/locale/intl.properties"
#else
#define SEARCHCONFIG_PROPERTIES "chrome://navigator/content/searchconfig.properties"
#define INTL_PROPERTIES "chrome://navigator/locale/navigator.properties"
#endif

static NS_DEFINE_CID(kRDFServiceCID,               NS_RDFSERVICE_CID);
static NS_DEFINE_CID(kRDFContainerCID,             NS_RDFCONTAINER_CID);
static NS_DEFINE_CID(kRDFContainerUtilsCID,        NS_RDFCONTAINERUTILS_CID);
static NS_DEFINE_CID(kRDFInMemoryDataSourceCID,    NS_RDFINMEMORYDATASOURCE_CID);
static NS_DEFINE_CID(kRDFXMLDataSourceCID,         NS_RDFXMLDATASOURCE_CID);
static NS_DEFINE_CID(kTextToSubURICID,             NS_TEXTTOSUBURI_CID);
static NS_DEFINE_CID(kPrefCID,                     NS_PREF_CID);

static const char kURINC_SearchEngineRoot[]                   = "NC:SearchEngineRoot";
static const char kURINC_SearchResultsSitesRoot[]             = "NC:SearchResultsSitesRoot";
static const char kURINC_LastSearchRoot[]                     = "NC:LastSearchRoot";
static const char kURINC_SearchCategoryRoot[]                 = "NC:SearchCategoryRoot";
static const char kURINC_SearchCategoryPrefix[]               = "NC:SearchCategory?category=";
static const char kURINC_SearchCategoryEnginePrefix[]         = "NC:SearchCategory?engine=";
static const char kURINC_SearchCategoryEngineBasenamePrefix[] = "NC:SearchCategory?engine=urn:search:engine:";
 
static const char kURINC_FilterSearchURLsRoot[]       = "NC:FilterSearchURLsRoot";
static const char kURINC_FilterSearchSitesRoot[]      = "NC:FilterSearchSitesRoot";
static const char kSearchCommand[]                    = "http://home.netscape.com/NC-rdf#command?";

int	PR_CALLBACK searchModePrefCallback(const char *pref, void *aClosure);

// helper routine because we need to rewrite this to use string
// iterators.. this replaces the old nsString::Find

static PRInt32 nsString_Find(const nsAString& aPattern,
                             const nsAString& aSource,
                             PRBool aIgnoreCase = PR_FALSE,
                             PRInt32 aOffset = 0, PRInt32 aCount = -1)
{
    nsAString::const_iterator start, end;
    aSource.BeginReading(start);
    aSource.EndReading(end);

    // now adjust for the parameters
    start.advance(aOffset);
    if (aCount>0) {
	end = start;		// note that start may have been advanced!
	end.advance(aCount);
    }
    PRBool found;
    if (aIgnoreCase)
	found = FindInReadable(aPattern, start, end,
			       nsCaseInsensitiveStringComparator());
    else
	found = FindInReadable(aPattern, start, end);

    if (!found)
	return kNotFound;

    nsAString::const_iterator originalStart;
    aSource.BeginReading(originalStart);
    return Distance(originalStart, start);
}

class	InternetSearchContext : public nsIInternetSearchContext
{
public:
			InternetSearchContext(PRUint32 contextType, nsIRDFResource *aParent, nsIRDFResource *aEngine,
				nsIUnicodeDecoder *aUnicodeDecoder, const PRUnichar *hint);
	virtual		~InternetSearchContext(void);
	NS_METHOD	Init();

	NS_DECL_ISUPPORTS
	NS_DECL_NSIINTERNETSEARCHCONTEXT

private:
	PRUint32			mContextType;
	nsCOMPtr<nsIRDFResource>	mParent;
	nsCOMPtr<nsIRDFResource>	mEngine;
	nsCOMPtr<nsIUnicodeDecoder>	mUnicodeDecoder;
	nsString			mBuffer;
	nsString			mHint;
};



InternetSearchContext::~InternetSearchContext(void)
{
}



InternetSearchContext::InternetSearchContext(PRUint32 contextType, nsIRDFResource *aParent, nsIRDFResource *aEngine,
				nsIUnicodeDecoder *aUnicodeDecoder, const PRUnichar *hint)
	: mContextType(contextType), mParent(aParent), mEngine(aEngine), mUnicodeDecoder(aUnicodeDecoder), mHint(hint)
{
}



NS_IMETHODIMP
InternetSearchContext::Init()
{
	return(NS_OK);
}



NS_IMETHODIMP
InternetSearchContext::GetContextType(PRUint32 *aContextType)
{
	*aContextType = mContextType;
	return(NS_OK);
}



NS_IMETHODIMP
InternetSearchContext::GetUnicodeDecoder(nsIUnicodeDecoder **decoder)
{
	*decoder = mUnicodeDecoder;
	NS_IF_ADDREF(*decoder);
	return(NS_OK);
}



NS_IMETHODIMP
InternetSearchContext::GetEngine(nsIRDFResource **node)
{
	*node = mEngine;
	NS_IF_ADDREF(*node);
	return(NS_OK);
}



NS_IMETHODIMP
InternetSearchContext::GetHintConst(const PRUnichar **hint)
{
	*hint = mHint.get();
	return(NS_OK);
}



NS_IMETHODIMP
InternetSearchContext::GetParent(nsIRDFResource **node)
{
	*node = mParent;
	NS_IF_ADDREF(*node);
	return(NS_OK);
}



NS_IMETHODIMP
InternetSearchContext::AppendBytes(const char *buffer, PRInt32 numBytes)
{
	mBuffer.AppendWithConversion(buffer, numBytes);
	return(NS_OK);
}



NS_IMETHODIMP
InternetSearchContext::AppendUnicodeBytes(const PRUnichar *buffer, PRInt32 numUniBytes)
{
	mBuffer.Append(buffer, numUniBytes);
	return(NS_OK);
}



NS_IMETHODIMP
InternetSearchContext::GetBufferConst(const PRUnichar **buffer)
{
	*buffer = mBuffer.get();
	return(NS_OK);
}



NS_IMETHODIMP
InternetSearchContext::GetBufferLength(PRInt32 *bufferLen)
{
	*bufferLen = mBuffer.Length();
	return(NS_OK);
}



NS_IMETHODIMP
InternetSearchContext::Truncate()
{
	mBuffer.Truncate();
	return(NS_OK);
}



NS_IMPL_THREADSAFE_ISUPPORTS1(InternetSearchContext, nsIInternetSearchContext)



nsresult
NS_NewInternetSearchContext(PRUint32 contextType, nsIRDFResource *aParent, nsIRDFResource *aEngine,
			    nsIUnicodeDecoder *aUnicodeDecoder, const PRUnichar *hint, nsIInternetSearchContext **aResult)
{
	 InternetSearchContext *result =
		 new InternetSearchContext(contextType, aParent, aEngine, aUnicodeDecoder, hint);

	 if (! result)
		 return NS_ERROR_OUT_OF_MEMORY;

	 nsresult rv = result->Init();
	 if (NS_FAILED(rv)) {
		 delete result;
		 return rv;
	 }

	 NS_ADDREF(result);
	 *aResult = result;
	 return NS_OK;
}





static const char		kEngineProtocol[] = "engine://";
static const char		kSearchProtocol[] = "internetsearch:";

#ifdef	DEBUG_SEARCH_UPDATES
#define	SEARCH_UPDATE_TIMEOUT	10000		// fire every 10 seconds
#else
#define	SEARCH_UPDATE_TIMEOUT	60000		// fire every 60 seconds
#endif


int PR_CALLBACK
searchModePrefCallback(const char *pref, void *aClosure)
{
	InternetSearchDataSource *searchDS = NS_STATIC_CAST(InternetSearchDataSource *, aClosure);
	if (!searchDS)	return(NS_OK);

	if (searchDS->prefs)
	{
		searchDS->prefs->GetIntPref(pref, &searchDS->gBrowserSearchMode);
#ifdef	DEBUG
		printf("searchModePrefCallback: '%s' = %d\n", pref, searchDS->gBrowserSearchMode);
#endif
		searchDS->Assert(searchDS->kNC_LastSearchRoot, searchDS->kNC_LastSearchMode, searchDS->kTrueLiteral, PR_TRUE);
	}
	return(NS_OK);
}

static	nsIRDFService		*gRDFService = nsnull;
static	nsIRDFContainerUtils	*gRDFC = nsnull;
PRInt32				InternetSearchDataSource::gRefCnt = 0;
PRInt32				InternetSearchDataSource::gBrowserSearchMode = 0;
nsIRDFDataSource		*InternetSearchDataSource::mInner = nsnull;
nsCOMPtr<nsISupportsArray>	InternetSearchDataSource::mUpdateArray;
nsCOMPtr<nsIRDFDataSource>	InternetSearchDataSource::mLocalstore;
nsCOMPtr<nsIRDFDataSource>	InternetSearchDataSource::categoryDataSource;
PRBool				InternetSearchDataSource::gEngineListBuilt = PR_FALSE;
#ifdef MOZ_PHOENIX
PRBool        InternetSearchDataSource::gReorderedEngineList = PR_FALSE;
#endif
nsCOMPtr<nsILoadGroup>		InternetSearchDataSource::mBackgroundLoadGroup;
nsCOMPtr<nsILoadGroup>		InternetSearchDataSource::mLoadGroup;
nsCOMPtr<nsIPref>		InternetSearchDataSource::prefs;

nsIRDFResource			*InternetSearchDataSource::kNC_SearchResult;
nsIRDFResource			*InternetSearchDataSource::kNC_SearchEngineRoot;
nsIRDFResource			*InternetSearchDataSource::kNC_LastSearchRoot;
nsIRDFResource			*InternetSearchDataSource::kNC_LastSearchMode;
nsIRDFResource			*InternetSearchDataSource::kNC_SearchCategoryRoot;
nsIRDFResource			*InternetSearchDataSource::kNC_SearchResultsSitesRoot;
nsIRDFResource			*InternetSearchDataSource::kNC_FilterSearchURLsRoot;
nsIRDFResource			*InternetSearchDataSource::kNC_FilterSearchSitesRoot;
nsIRDFResource			*InternetSearchDataSource::kNC_SearchType;
nsIRDFResource			*InternetSearchDataSource::kNC_Ref;
nsIRDFResource			*InternetSearchDataSource::kNC_Child;
nsIRDFResource			*InternetSearchDataSource::kNC_Title;
nsIRDFResource			*InternetSearchDataSource::kNC_Data;
nsIRDFResource			*InternetSearchDataSource::kNC_Name;
nsIRDFResource			*InternetSearchDataSource::kNC_Description;
nsIRDFResource			*InternetSearchDataSource::kNC_Version;
nsIRDFResource			*InternetSearchDataSource::kNC_actionButton;
nsIRDFResource			*InternetSearchDataSource::kNC_actionBar;
nsIRDFResource			*InternetSearchDataSource::kNC_searchForm;
nsIRDFResource			*InternetSearchDataSource::kNC_LastText;
nsIRDFResource			*InternetSearchDataSource::kNC_URL;
nsIRDFResource			*InternetSearchDataSource::kRDF_InstanceOf;
nsIRDFResource			*InternetSearchDataSource::kRDF_type;
nsIRDFResource			*InternetSearchDataSource::kNC_loading;
nsIRDFResource			*InternetSearchDataSource::kNC_HTML;
nsIRDFResource			*InternetSearchDataSource::kNC_Icon;
nsIRDFResource			*InternetSearchDataSource::kNC_StatusIcon;
nsIRDFResource			*InternetSearchDataSource::kNC_Banner;
nsIRDFResource			*InternetSearchDataSource::kNC_Site;
nsIRDFResource			*InternetSearchDataSource::kNC_Relevance;
nsIRDFResource			*InternetSearchDataSource::kNC_RelevanceSort;
nsIRDFResource			*InternetSearchDataSource::kNC_Date;
nsIRDFResource			*InternetSearchDataSource::kNC_PageRank;
nsIRDFResource			*InternetSearchDataSource::kNC_Engine;
nsIRDFResource			*InternetSearchDataSource::kNC_Price;
nsIRDFResource			*InternetSearchDataSource::kNC_PriceSort;
nsIRDFResource			*InternetSearchDataSource::kNC_Availability;
nsIRDFResource			*InternetSearchDataSource::kNC_BookmarkSeparator;
nsIRDFResource			*InternetSearchDataSource::kNC_Update;
nsIRDFResource			*InternetSearchDataSource::kNC_UpdateIcon;
nsIRDFResource			*InternetSearchDataSource::kNC_UpdateCheckDays;
nsIRDFResource			*InternetSearchDataSource::kWEB_LastPingDate;
nsIRDFResource			*InternetSearchDataSource::kWEB_LastPingModDate;
nsIRDFResource			*InternetSearchDataSource::kWEB_LastPingContentLen;

nsIRDFResource			*InternetSearchDataSource::kNC_SearchCommand_AddToBookmarks;
nsIRDFResource			*InternetSearchDataSource::kNC_SearchCommand_AddQueryToBookmarks;
nsIRDFResource			*InternetSearchDataSource::kNC_SearchCommand_FilterResult;
nsIRDFResource			*InternetSearchDataSource::kNC_SearchCommand_FilterSite;
nsIRDFResource			*InternetSearchDataSource::kNC_SearchCommand_ClearFilters;

nsIRDFLiteral			*InternetSearchDataSource::kTrueLiteral;
InternetSearchDataSource::InternetSearchDataSource(void)
{
	if (gRefCnt++ == 0)
	{
		nsresult rv = CallGetService(kRDFServiceCID, &gRDFService);
		NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get RDF service");

		rv = CallGetService(kRDFContainerUtilsCID, &gRDFC);
		NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get RDF container utils");

		gRDFService->GetResource(NS_LITERAL_CSTRING(kURINC_SearchEngineRoot),
                             &kNC_SearchEngineRoot);
		gRDFService->GetResource(NS_LITERAL_CSTRING(kURINC_LastSearchRoot),
                             &kNC_LastSearchRoot);
		gRDFService->GetResource(NS_LITERAL_CSTRING(kURINC_SearchResultsSitesRoot),
                             &kNC_SearchResultsSitesRoot);
		gRDFService->GetResource(NS_LITERAL_CSTRING(kURINC_FilterSearchURLsRoot),
                             &kNC_FilterSearchURLsRoot);
		gRDFService->GetResource(NS_LITERAL_CSTRING(kURINC_FilterSearchSitesRoot),
                             &kNC_FilterSearchSitesRoot);
		gRDFService->GetResource(NS_LITERAL_CSTRING(kURINC_SearchCategoryRoot),
                             &kNC_SearchCategoryRoot);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "SearchMode"),
                             &kNC_LastSearchMode);

		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "searchtype"),
                             &kNC_SearchType);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "SearchResult"),
                             &kNC_SearchResult);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "ref"),
                             &kNC_Ref);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "child"),
                             &kNC_Child);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "title"),
                             &kNC_Title);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "data"),
                             &kNC_Data);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Name"),
                             &kNC_Name);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Description"),
                             &kNC_Description);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Version"),
                             &kNC_Version);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "actionButton"),
                             &kNC_actionButton);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "actionBar"),
                             &kNC_actionBar);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "searchForm"),
                             &kNC_searchForm);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "LastText"),
                             &kNC_LastText);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "URL"),
                             &kNC_URL);
		gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "instanceOf"),
                             &kRDF_InstanceOf);
		gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "type"),
                             &kRDF_type);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "loading"),
                             &kNC_loading);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "HTML"),
                             &kNC_HTML);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Icon"),
                             &kNC_Icon);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "StatusIcon"),
                             &kNC_StatusIcon);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Banner"),
                             &kNC_Banner);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Site"),
                             &kNC_Site);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Relevance"),
                             &kNC_Relevance);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Relevance?sort=true"),
                             &kNC_RelevanceSort);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Date"),
                             &kNC_Date);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "PageRank"),
                             &kNC_PageRank);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Engine"),
                             &kNC_Engine);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Price"),
                             &kNC_Price);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Price?sort=true"),
                             &kNC_PriceSort);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Availability"),
                             &kNC_Availability);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "BookmarkSeparator"),
                             &kNC_BookmarkSeparator);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Update"),
                             &kNC_Update);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "UpdateIcon"),
                             &kNC_UpdateIcon);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "UpdateCheckDays"),
                             &kNC_UpdateCheckDays);
		gRDFService->GetResource(NS_LITERAL_CSTRING(WEB_NAMESPACE_URI "LastPingDate"),
                             &kWEB_LastPingDate);
		gRDFService->GetResource(NS_LITERAL_CSTRING(WEB_NAMESPACE_URI "LastPingModDate"),
                             &kWEB_LastPingModDate);
		gRDFService->GetResource(NS_LITERAL_CSTRING(WEB_NAMESPACE_URI "LastPingContentLen"),
                             &kWEB_LastPingContentLen);

		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "command?cmd=addtobookmarks"),
                             &kNC_SearchCommand_AddToBookmarks);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "command?cmd=addquerytobookmarks"),
                             &kNC_SearchCommand_AddQueryToBookmarks);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "command?cmd=filterresult"),
                             &kNC_SearchCommand_FilterResult);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "command?cmd=filtersite"),
                             &kNC_SearchCommand_FilterSite);
		gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "command?cmd=clearfilters"),
                             &kNC_SearchCommand_ClearFilters);

		gRDFService->GetLiteral(NS_LITERAL_STRING("true").get(), &kTrueLiteral);

		prefs = do_GetService(kPrefCID);
		if (prefs)
		{
			prefs->RegisterCallback("browser.search.mode", searchModePrefCallback, this);
			prefs->GetIntPref("browser.search.mode", &gBrowserSearchMode);
		}
	}
}



InternetSearchDataSource::~InternetSearchDataSource (void)
{
	if (--gRefCnt == 0)
	{
		NS_IF_RELEASE(kNC_SearchResult);
		NS_IF_RELEASE(kNC_SearchEngineRoot);
		NS_IF_RELEASE(kNC_LastSearchRoot);
		NS_IF_RELEASE(kNC_LastSearchMode);
		NS_IF_RELEASE(kNC_SearchCategoryRoot);
		NS_IF_RELEASE(kNC_SearchResultsSitesRoot);
		NS_IF_RELEASE(kNC_FilterSearchURLsRoot);
		NS_IF_RELEASE(kNC_FilterSearchSitesRoot);
		NS_IF_RELEASE(kNC_SearchType);
		NS_IF_RELEASE(kNC_Ref);
		NS_IF_RELEASE(kNC_Child);
		NS_IF_RELEASE(kNC_Title);
		NS_IF_RELEASE(kNC_Data);
		NS_IF_RELEASE(kNC_Name);
		NS_IF_RELEASE(kNC_Description);
		NS_IF_RELEASE(kNC_Version);
		NS_IF_RELEASE(kNC_actionButton);
		NS_IF_RELEASE(kNC_actionBar);
		NS_IF_RELEASE(kNC_searchForm);
		NS_IF_RELEASE(kNC_LastText);
		NS_IF_RELEASE(kNC_URL);
		NS_IF_RELEASE(kRDF_InstanceOf);
		NS_IF_RELEASE(kRDF_type);
		NS_IF_RELEASE(kNC_loading);
		NS_IF_RELEASE(kNC_HTML);
		NS_IF_RELEASE(kNC_Icon);
		NS_IF_RELEASE(kNC_StatusIcon);
		NS_IF_RELEASE(kNC_Banner);
		NS_IF_RELEASE(kNC_Site);
		NS_IF_RELEASE(kNC_Relevance);
		NS_IF_RELEASE(kNC_RelevanceSort);
		NS_IF_RELEASE(kNC_Date);
		NS_IF_RELEASE(kNC_PageRank);
		NS_IF_RELEASE(kNC_Engine);
		NS_IF_RELEASE(kNC_Price);
		NS_IF_RELEASE(kNC_PriceSort);
		NS_IF_RELEASE(kNC_Availability);
		NS_IF_RELEASE(kNC_BookmarkSeparator);
		NS_IF_RELEASE(kNC_Update);
		NS_IF_RELEASE(kNC_UpdateIcon);
		NS_IF_RELEASE(kNC_UpdateCheckDays);
		NS_IF_RELEASE(kWEB_LastPingDate);
		NS_IF_RELEASE(kWEB_LastPingModDate);
		NS_IF_RELEASE(kWEB_LastPingContentLen);

		NS_IF_RELEASE(kNC_SearchCommand_AddToBookmarks);
		NS_IF_RELEASE(kNC_SearchCommand_AddQueryToBookmarks);
		NS_IF_RELEASE(kNC_SearchCommand_FilterResult);
		NS_IF_RELEASE(kNC_SearchCommand_FilterSite);
		NS_IF_RELEASE(kNC_SearchCommand_ClearFilters);

		NS_IF_RELEASE(kTrueLiteral);

		NS_IF_RELEASE(mInner);

		mUpdateArray = nsnull;
		mLocalstore = nsnull;
		mBackgroundLoadGroup = nsnull;
		mLoadGroup = nsnull;
		categoryDataSource = nsnull;		

		if (mTimer)
		{
			// be sure to cancel the timer, as it holds a
			// weak reference back to InternetSearchDataSource
			mTimer->Cancel();
			mTimer = nsnull;
		}

		if (prefs)
		{
			prefs->UnregisterCallback("browser.search.mode", searchModePrefCallback, this);
			prefs = nsnull;
		}

    NS_IF_RELEASE(gRDFC);

		if (gRDFService)
		{
			gRDFService->UnregisterDataSource(this);
      NS_RELEASE(gRDFService);
		}
	}
}



nsresult
InternetSearchDataSource::GetSearchEngineToPing(nsIRDFResource **theEngine, nsCString &updateURL)
{
	nsresult	rv = NS_OK;

	*theEngine = nsnull;
	updateURL.Truncate();

	if (!mUpdateArray)	return(NS_OK);

	PRUint32	numEngines = 0;
	if (NS_FAILED(rv = mUpdateArray->Count(&numEngines)))	return(rv);
	if (numEngines < 1)	return(NS_OK);

	nsCOMPtr<nsIRDFResource> aRes (do_QueryElementAt(mUpdateArray, 0));

	// note: important to remove element from array
	mUpdateArray->RemoveElementAt(0);

	if (aRes)
	{
		if (isSearchCategoryEngineURI(aRes))
		{
			nsCOMPtr<nsIRDFResource>	trueEngine;
			rv = resolveSearchCategoryEngineURI(aRes, getter_AddRefs(trueEngine));
			if (NS_FAILED(rv) || (rv == NS_RDF_NO_VALUE))	return(rv);
			if (!trueEngine)	return(NS_RDF_NO_VALUE);

			aRes = trueEngine;
		}

		if (!aRes)	return(NS_OK);

		*theEngine = aRes.get();
		NS_ADDREF(*theEngine);

		// get update URL
		nsCOMPtr<nsIRDFNode>	aNode;
		if (NS_SUCCEEDED(rv = mInner->GetTarget(aRes, kNC_Update, PR_TRUE, getter_AddRefs(aNode)))
				&& (rv != NS_RDF_NO_VALUE))
		{
			nsCOMPtr<nsIRDFLiteral>	aLiteral (do_QueryInterface(aNode));
			if (aLiteral)
			{
				const PRUnichar	*updateUni = nsnull;
				aLiteral->GetValueConst(&updateUni);
				if (updateUni)
				{
					updateURL.AssignWithConversion(updateUni);
				}
			}
		}
	}
	return(rv);
}



void
InternetSearchDataSource::FireTimer(nsITimer* aTimer, void* aClosure)
{
	InternetSearchDataSource *search = NS_STATIC_CAST(InternetSearchDataSource *, aClosure);
	if (!search)	return;

  if (!search->busySchedule)
	{
		nsresult			rv;
		nsCOMPtr<nsIRDFResource>	searchURI;
		nsCAutoString			updateURL;
		if (NS_FAILED(rv = search->GetSearchEngineToPing(getter_AddRefs(searchURI), updateURL)))
			return;
		if (!searchURI)			return;
		if (updateURL.IsEmpty())	return;

		search->busyResource = searchURI;

		nsCOMPtr<nsIInternetSearchContext>	engineContext;
		if (NS_FAILED(rv = NS_NewInternetSearchContext(nsIInternetSearchContext::ENGINE_UPDATE_HEAD_CONTEXT,
			nsnull, searchURI, nsnull, nsnull, getter_AddRefs(engineContext))))
			return;
		if (!engineContext)	return;

		nsCOMPtr<nsIURI>	uri;
		if (NS_FAILED(rv = NS_NewURI(getter_AddRefs(uri), updateURL.get())))	return;

		nsCOMPtr<nsIChannel>	channel;
		if (NS_FAILED(rv = NS_NewChannel(getter_AddRefs(channel), uri, nsnull)))	return;

		channel->SetLoadFlags(nsIRequest::VALIDATE_ALWAYS);

		nsCOMPtr<nsIHttpChannel> httpChannel (do_QueryInterface(channel));
		if (!httpChannel)	return;

		// rjc says: just check "HEAD" info for whether a search file has changed
        httpChannel->SetRequestMethod(NS_LITERAL_CSTRING("HEAD"));
		if (NS_SUCCEEDED(rv = channel->AsyncOpen(search, engineContext)))
		{
			search->busySchedule = PR_TRUE;

#ifdef	DEBUG_SEARCH_UPDATES
			printf("    InternetSearchDataSource::FireTimer - Pinging '%s'\n", (char *)updateURL.get());
#endif
		}
	}
#ifdef	DEBUG_SEARCH_UPDATES
else
	{
	printf("    InternetSearchDataSource::FireTimer - busy pinging.\n");
	}
#endif
}



PRBool
InternetSearchDataSource::isEngineURI(nsIRDFResource *r)
{
	PRBool		isEngineURIFlag = PR_FALSE;
	const char	*uri = nsnull;
	
	r->GetValueConst(&uri);
	if ((uri) && (!strncmp(uri, kEngineProtocol, sizeof(kEngineProtocol) - 1)))
	{
		isEngineURIFlag = PR_TRUE;
	}
	return(isEngineURIFlag);
}



PRBool
InternetSearchDataSource::isSearchURI(nsIRDFResource *r)
{
	PRBool		isSearchURIFlag = PR_FALSE;
	const char	*uri = nsnull;
	
	r->GetValueConst(&uri);
	if ((uri) && (!strncmp(uri, kSearchProtocol, sizeof(kSearchProtocol) - 1)))
	{
		isSearchURIFlag = PR_TRUE;
	}
	return(isSearchURIFlag);
}



PRBool
InternetSearchDataSource::isSearchCategoryURI(nsIRDFResource *r)
{
	PRBool		isSearchCategoryURIFlag = PR_FALSE;
	const char	*uri = nsnull;
	
	r->GetValueConst(&uri);
	if ((uri) && (!strncmp(uri, kURINC_SearchCategoryPrefix, sizeof(kURINC_SearchCategoryPrefix) - 1)))
	{
		isSearchCategoryURIFlag = PR_TRUE;
	}
	return(isSearchCategoryURIFlag);
}



PRBool
InternetSearchDataSource::isSearchCategoryEngineURI(nsIRDFResource *r)
{
	PRBool		isSearchCategoryEngineURIFlag = PR_FALSE;
	const char	*uri = nsnull;
	
	r->GetValueConst(&uri);
	if ((uri) && (!strncmp(uri, kURINC_SearchCategoryEnginePrefix, sizeof(kURINC_SearchCategoryEnginePrefix) - 1)))
	{
		isSearchCategoryEngineURIFlag = PR_TRUE;
	}
	return(isSearchCategoryEngineURIFlag);
}



PRBool
InternetSearchDataSource::isSearchCategoryEngineBasenameURI(nsIRDFNode *r)
{
	PRBool		isSearchCategoryEngineBasenameURIFlag = PR_FALSE;

	nsCOMPtr<nsIRDFResource> aRes (do_QueryInterface(r));
	if (aRes)
	{
		const char	*uri = nsnull;
		aRes->GetValueConst(&uri);
		if ((uri) && (!nsCRT::strncmp(uri, kURINC_SearchCategoryEngineBasenamePrefix,
			(int)sizeof(kURINC_SearchCategoryEngineBasenamePrefix) - 1)))
		{
			isSearchCategoryEngineBasenameURIFlag = PR_TRUE;
		}
	}
	else
	{
		nsCOMPtr<nsIRDFLiteral>	aLit (do_QueryInterface(r));
		if (aLit)
		{
			const	PRUnichar	*uriUni = nsnull;
			aLit->GetValueConst(&uriUni);
			if ((uriUni) && (!nsCRT::strncmp(uriUni,
							 NS_ConvertASCIItoUCS2(kURINC_SearchCategoryEngineBasenamePrefix).get(),
				(int)sizeof(kURINC_SearchCategoryEngineBasenamePrefix) - 1)))
			{
				isSearchCategoryEngineBasenameURIFlag = PR_TRUE;
			}
		}
	}
	return(isSearchCategoryEngineBasenameURIFlag);
}



PRBool
InternetSearchDataSource::isSearchCommand(nsIRDFResource *r)
{
	PRBool		isSearchCommandFlag = PR_FALSE;
	const char	*uri = nsnull;
	
	if (NS_SUCCEEDED(r->GetValueConst( &uri )) && (uri))
	{
		if (!strncmp(uri, kSearchCommand, sizeof(kSearchCommand) - 1))
		{
			isSearchCommandFlag = PR_TRUE;
		}
	}
	return(isSearchCommandFlag);
}



nsresult
InternetSearchDataSource::resolveSearchCategoryEngineURI(nsIRDFResource *engine, nsIRDFResource **trueEngine)
{
	*trueEngine = nsnull;

	if ((!categoryDataSource) || (!mInner))	return(NS_ERROR_UNEXPECTED);

	nsresult	rv;
	const char	*uriUni = nsnull;
	if (NS_FAILED(rv = engine->GetValueConst(&uriUni)))	return(rv);
	if (!uriUni)	return(NS_ERROR_NULL_POINTER);

	nsAutoString		uri;
	uri.AssignWithConversion(uriUni);
	if (uri.Find(kURINC_SearchCategoryEngineBasenamePrefix) !=0)	return(NS_ERROR_UNEXPECTED);

	nsCOMPtr<nsIRDFLiteral>	basenameLiteral;
	if (NS_FAILED(rv = gRDFService->GetLiteral(uri.get(),
			getter_AddRefs(basenameLiteral))))	return(rv);

	nsCOMPtr<nsIRDFResource>	catSrc;
	rv = mInner->GetSource(kNC_URL, basenameLiteral, PR_TRUE, getter_AddRefs(catSrc));
	if (NS_FAILED(rv) || (rv == NS_RDF_NO_VALUE))	return(rv);
	if (!catSrc)		return(NS_ERROR_UNEXPECTED);

	*trueEngine = catSrc;
	NS_IF_ADDREF(*trueEngine);
	return(NS_OK);
}



////////////////////////////////////////////////////////////////////////

NS_IMPL_ADDREF(InternetSearchDataSource)
NS_IMPL_RELEASE(InternetSearchDataSource)

NS_INTERFACE_MAP_BEGIN(InternetSearchDataSource)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInternetSearchService)
   NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
   NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
   NS_INTERFACE_MAP_ENTRY(nsIInternetSearchService)
   NS_INTERFACE_MAP_ENTRY(nsIRDFDataSource)
   NS_INTERFACE_MAP_ENTRY(nsIObserver)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_END


NS_METHOD
InternetSearchDataSource::Init()
{
	nsresult	rv = NS_ERROR_OUT_OF_MEMORY;

	if (NS_FAILED(rv = CallCreateInstance(kRDFInMemoryDataSourceCID, &mInner)))
		return(rv);

	// get localstore, as we'll be using it
	if (NS_FAILED(rv = gRDFService->GetDataSource("rdf:local-store", getter_AddRefs(mLocalstore))))
		return(rv);

	if (NS_FAILED(rv = NS_NewISupportsArray(getter_AddRefs(mUpdateArray))))
		return(rv);

	// register this as a named data source with the service manager
	if (NS_FAILED(rv = gRDFService->RegisterDataSource(this, PR_FALSE)))
		return(rv);

	rv = NS_NewLoadGroup(getter_AddRefs(mLoadGroup), nsnull);
	NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create load group");

	if (!mTimer)
	{
		busySchedule = PR_FALSE;
		mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
		if (mTimer)
		{
			mTimer->InitWithFuncCallback(InternetSearchDataSource::FireTimer, this,
                                                     SEARCH_UPDATE_TIMEOUT, nsITimer::TYPE_REPEATING_SLACK);
			// Note: don't addref "this" as we'll cancel the timer in the
			//       InternetSearchDataSource destructor
		}
	}

	gEngineListBuilt = PR_FALSE;

	// Register as a profile change obsevrer
  nsCOMPtr<nsIObserverService> observerService = 
           do_GetService("@mozilla.org/observer-service;1", &rv);
  if (observerService) {
    observerService->AddObserver(this, "profile-before-change", PR_TRUE);
    observerService->AddObserver(this, "profile-do-change", PR_TRUE);
  }

	return(rv);
}

void
InternetSearchDataSource::DeferredInit()
{
  if (gEngineListBuilt)
    return;

  nsresult rv;

  nsCOMPtr<nsIProperties> dirSvc
    (do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID));
  if (!dirSvc)
    return;

  gEngineListBuilt = PR_TRUE;

  // get available search engines
  nsCOMPtr<nsISimpleEnumerator> dirlist;
  rv = dirSvc->Get(NS_APP_SEARCH_DIR_LIST,
                   NS_GET_IID(nsISimpleEnumerator), getter_AddRefs(dirlist));
  if (NS_SUCCEEDED(rv))
  {
    PRBool more;
    while (NS_SUCCEEDED(dirlist->HasMoreElements(&more)) && more) {
      nsCOMPtr<nsISupports> suppfile;
      nsCOMPtr<nsIFile> dir;
      dirlist->GetNext(getter_AddRefs(suppfile));
      dir = do_QueryInterface(suppfile);
      if (dir)
      {
        GetSearchEngineList(dir, PR_FALSE);
      }
    }
  }

  // read in category list
  GetCategoryList();
}

NS_IMETHODIMP
InternetSearchDataSource::GetURI(char **uri)
{
	NS_PRECONDITION(uri != nsnull, "null ptr");
	if (! uri)
		return NS_ERROR_NULL_POINTER;

	if ((*uri = nsCRT::strdup("rdf:internetsearch")) == nsnull)
		return NS_ERROR_OUT_OF_MEMORY;

	return NS_OK;
}



NS_IMETHODIMP
InternetSearchDataSource::GetSource(nsIRDFResource* property,
                                nsIRDFNode* target,
                                PRBool tv,
                                nsIRDFResource** source /* out */)
{
	NS_PRECONDITION(property != nsnull, "null ptr");
	if (! property)
		return NS_ERROR_NULL_POINTER;

	NS_PRECONDITION(target != nsnull, "null ptr");
	if (! target)
		return NS_ERROR_NULL_POINTER;

	NS_PRECONDITION(source != nsnull, "null ptr");
	if (! source)
		return NS_ERROR_NULL_POINTER;

	*source = nsnull;
	return NS_RDF_NO_VALUE;
}



NS_IMETHODIMP
InternetSearchDataSource::GetSources(nsIRDFResource *property,
                                 nsIRDFNode *target,
                                 PRBool tv,
                                 nsISimpleEnumerator **sources /* out */)
{
	nsresult	rv = NS_RDF_NO_VALUE;

	if (mInner)
	{
		rv = mInner->GetSources(property, target, tv, sources);
	}
	else
	{
		rv = NS_NewEmptyEnumerator(sources);
	}
	return(rv);
}



NS_IMETHODIMP
InternetSearchDataSource::GetTarget(nsIRDFResource *source,
                                nsIRDFResource *property,
                                PRBool tv,
                                nsIRDFNode **target /* out */)
{
	NS_PRECONDITION(source != nsnull, "null ptr");
	if (! source)
		return NS_ERROR_NULL_POINTER;

	NS_PRECONDITION(property != nsnull, "null ptr");
	if (! property)
		return NS_ERROR_NULL_POINTER;

	NS_PRECONDITION(target != nsnull, "null ptr");
	if (! target)
		return NS_ERROR_NULL_POINTER;

	*target = nsnull;

	nsresult		rv = NS_RDF_NO_VALUE;

	// we only have positive assertions in the internet search data source.
	if (! tv)
		return(rv);

	if ((isSearchCategoryURI(source)) && (categoryDataSource))
	{
		const char	*uri = nsnull;
		source->GetValueConst(&uri);
		if (!uri)	return(NS_ERROR_UNEXPECTED);
		nsAutoString	catURI;
		catURI.AssignWithConversion(uri);

		nsCOMPtr<nsIRDFResource>	category;
		nsCAutoString			caturiC;
		caturiC.AssignWithConversion(catURI);
		if (NS_FAILED(rv = gRDFService->GetResource(caturiC,
			getter_AddRefs(category))))
			return(rv);

		rv = categoryDataSource->GetTarget(category, property, tv, target);
		return(rv);
	}

	if (isSearchCategoryEngineURI(source))
	{
		nsCOMPtr<nsIRDFResource>	trueEngine;
		rv = resolveSearchCategoryEngineURI(source, getter_AddRefs(trueEngine));
		if (NS_FAILED(rv) || (rv == NS_RDF_NO_VALUE))	return(rv);
		if (!trueEngine)	return(NS_RDF_NO_VALUE);

		source = trueEngine;
	}

	if (isSearchURI(source) && (property == kNC_Child))
	{
		// fake out the generic builder (i.e. return anything in this case)
		// so that search containers never appear to be empty
		*target = source;
		NS_ADDREF(source);
		return(NS_OK);
	}

	if (isSearchCommand(source) && (property == kNC_Name))
	{
    nsresult rv;
    nsCOMPtr<nsIStringBundleService>
    stringService(do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv));

    if (NS_SUCCEEDED(rv) && stringService) {

      nsCOMPtr<nsIStringBundle> bundle;
      rv = stringService->CreateBundle(SEARCH_PROPERTIES, getter_AddRefs(bundle));
      if (NS_SUCCEEDED(rv) && bundle) {

        nsXPIDLString valUni;
        nsAutoString name;

        if (source == kNC_SearchCommand_AddToBookmarks)
          name.AssignLiteral("addtobookmarks");
        else if (source == kNC_SearchCommand_AddQueryToBookmarks)
          name.AssignLiteral("addquerytobookmarks");
        else if (source == kNC_SearchCommand_FilterResult)
          name.AssignLiteral("excludeurl");
        else if (source == kNC_SearchCommand_FilterSite)
          name.AssignLiteral("excludedomain");
        else if (source == kNC_SearchCommand_ClearFilters)
          name.AssignLiteral("clearfilters");

        rv = bundle->GetStringFromName(name.get(), getter_Copies(valUni));
        if (NS_SUCCEEDED(rv) && valUni && *valUni) {
          *target = nsnull;
          nsCOMPtr<nsIRDFLiteral> literal;
          if (NS_FAILED(rv = gRDFService->GetLiteral(valUni, getter_AddRefs(literal))))
            return rv;
          *target = literal;
          NS_IF_ADDREF(*target);
          return rv;
        }
      }
    }
  }

	if (isEngineURI(source))
	{
		// if we're asking for info on a search engine, (deferred) load it if needed
		nsCOMPtr<nsIRDFLiteral>	dataLit;
		FindData(source, getter_AddRefs(dataLit));
	}

	if (mInner)
	{
		rv = mInner->GetTarget(source, property, tv, target);
	}

	return(rv);
}

#ifdef MOZ_PHOENIX
void
InternetSearchDataSource::ReorderEngineList()
{
  // XXXben - a temporary, inelegant solution to search list ordering until 
  //          after 1.0 when we replace all of this with something better 
  //          suited to our needs. 
  nsresult rv;
  nsCOMArray<nsIRDFResource> engineList;

  // Load all data for sherlock files...
  nsCOMPtr<nsISimpleEnumerator> engines;
  rv = GetTargets(kNC_SearchEngineRoot, kNC_Child, PR_TRUE, getter_AddRefs(engines));
  if (NS_FAILED(rv)) return; // Not Fatal. 

  do {
    PRBool hasMore;
    engines->HasMoreElements(&hasMore);
    if (!hasMore)
      break;

    nsCOMPtr<nsISupports> supp;
    engines->GetNext(getter_AddRefs(supp));
    nsCOMPtr<nsIRDFResource> engineResource(do_QueryInterface(supp));

    nsCOMPtr<nsIRDFLiteral> data;
    FindData(engineResource, getter_AddRefs(data));
  }
  while (PR_TRUE);

  // Build the ordered items first...
  nsCOMPtr<nsIPrefBranch> pserv(do_QueryInterface(prefs));
  char prefNameBuf[1096];
  PRInt32 i = 0; 
  do {
    ++i;
    sprintf(prefNameBuf, "browser.search.order.%d", i);

    nsCOMPtr<nsIPrefLocalizedString> engineName;
    rv = pserv->GetComplexValue(prefNameBuf, 
                                NS_GET_IID(nsIPrefLocalizedString),
                                getter_AddRefs(engineName));
    if (NS_FAILED(rv)) break;

    nsXPIDLString data;
    engineName->GetData(getter_Copies(data));

    nsCOMPtr<nsIRDFLiteral> engineNameLiteral;
    gRDFService->GetLiteral(data, getter_AddRefs(engineNameLiteral));

    nsCOMPtr<nsIRDFResource> engineResource;
    rv = mInner->GetSource(kNC_Name, engineNameLiteral, PR_TRUE, getter_AddRefs(engineResource));
    if (NS_FAILED(rv)) continue;

    engineList.AppendObject(engineResource);
  }
  while (PR_TRUE);

  // Now add the rest...
  rv = GetTargets(kNC_SearchEngineRoot, kNC_Child, PR_TRUE, getter_AddRefs(engines));
  if (NS_FAILED(rv)) return; // Not Fatal. 

  do {
    PRBool hasMore;
    engines->HasMoreElements(&hasMore);
    if (!hasMore)
      break;

    nsCOMPtr<nsISupports> supp;
    engines->GetNext(getter_AddRefs(supp));
    nsCOMPtr<nsIRDFResource> engineResource(do_QueryInterface(supp));

    if (engineList.IndexOfObject(engineResource) == -1) 
      engineList.AppendObject(engineResource);

    // Unhook the item from the list, so we can rebuild it in the right
    // order. Failures are benign.
    Unassert(kNC_SearchEngineRoot, kNC_Child, engineResource);
  }
  while (PR_TRUE);

  PRInt32 engineCount = engineList.Count();
  for (i = 0; i < engineCount; ++i)
    Assert(kNC_SearchEngineRoot, kNC_Child, engineList[i], PR_TRUE);

  gReorderedEngineList = PR_TRUE;
}
#endif

NS_IMETHODIMP
InternetSearchDataSource::GetTargets(nsIRDFResource *source,
                           nsIRDFResource *property,
                           PRBool tv,
                           nsISimpleEnumerator **targets /* out */)
{
	NS_PRECONDITION(source != nsnull, "null ptr");
	if (! source)
		return NS_ERROR_NULL_POINTER;

	NS_PRECONDITION(property != nsnull, "null ptr");
	if (! property)
		return NS_ERROR_NULL_POINTER;

	NS_PRECONDITION(targets != nsnull, "null ptr");
	if (! targets)
		return NS_ERROR_NULL_POINTER;

	nsresult rv = NS_RDF_NO_VALUE;

	// we only have positive assertions in the internet search data source.
	if (! tv)
		return(rv);

	if ((isSearchCategoryURI(source)) && (categoryDataSource))
	{
		const char	*uri = nsnull;
		source->GetValueConst(&uri);
		if (!uri)	return(NS_ERROR_UNEXPECTED);
		nsAutoString	catURI;
		catURI.AssignWithConversion(uri);

		nsCOMPtr<nsIRDFResource>	category;
		nsCAutoString			caturiC;
		caturiC.AssignWithConversion(catURI);
		if (NS_FAILED(rv = gRDFService->GetResource(caturiC,
			getter_AddRefs(category))))
			return(rv);

		rv = categoryDataSource->GetTargets(category, property, tv, targets);
		return(rv);
	}

	if (isSearchCategoryEngineURI(source))
	{
		nsCOMPtr<nsIRDFResource>	trueEngine;
		rv = resolveSearchCategoryEngineURI(source, getter_AddRefs(trueEngine));
		if (NS_FAILED(rv) || (rv == NS_RDF_NO_VALUE))	return(rv);
		if (!trueEngine)	return(NS_RDF_NO_VALUE);

		source = trueEngine;
	}

	if (mInner)
	{	
		// defer search engine discovery until needed; small startup time improvement
    if ((source == kNC_SearchEngineRoot || isSearchURI(source)) &&
        property == kNC_Child && !gEngineListBuilt)
		{
			DeferredInit();
		}

    rv = mInner->GetTargets(source, property, tv, targets);
	}
	if (isSearchURI(source))
	{
		if (property == kNC_Child)
		{
			PRBool		doNetworkRequest = PR_TRUE;
			if (NS_SUCCEEDED(rv) && (targets))
			{
				// check and see if we already have data for the search in question;
				// if we do, BeginSearchRequest() won't bother doing the search again,
				// otherwise it will kickstart it
				PRBool		hasResults = PR_FALSE;
        if (NS_SUCCEEDED((*targets)->HasMoreElements(&hasResults)) &&
            hasResults)
				{
					doNetworkRequest = PR_FALSE;
				}
			}
			BeginSearchRequest(source, doNetworkRequest);
		}
	}
	return(rv);
}



nsresult
InternetSearchDataSource::GetCategoryList()
{
	nsIRDFDataSource	*ds;
	nsresult rv = CallCreateInstance(kRDFXMLDataSourceCID, &ds);
	if (NS_FAILED(rv))	return(rv);
	if (!ds)		return(NS_ERROR_UNEXPECTED);

	categoryDataSource = do_QueryInterface(ds);
	NS_RELEASE(ds);
	ds = nsnull;
	if (!categoryDataSource)	return(NS_ERROR_UNEXPECTED);

	nsCOMPtr<nsIRDFRemoteDataSource> remoteCategoryDataSource (do_QueryInterface(categoryDataSource));
	if (!remoteCategoryDataSource)	return(NS_ERROR_UNEXPECTED);

	// get search.rdf
		
  nsCOMPtr<nsIFile> searchFile;
  nsCAutoString searchFileURLSpec;

  rv = NS_GetSpecialDirectory(NS_APP_SEARCH_50_FILE, getter_AddRefs(searchFile));
  if (NS_FAILED(rv)) return rv;
  NS_GetURLSpecFromFile(searchFile, searchFileURLSpec);
  if (NS_FAILED(rv)) return rv;
	rv = remoteCategoryDataSource->Init(searchFileURLSpec.get());
  if (NS_FAILED(rv)) return rv;
    
	// synchronous read
	rv = remoteCategoryDataSource->Refresh(PR_TRUE);
	if (NS_FAILED(rv))	return(rv);

	// ensure that all search engine references actually exist; if not, forget about them

	PRBool				isDirtyFlag = PR_FALSE;
	nsCOMPtr<nsIRDFContainer> 	categoryRoot;
	rv = gRDFC->MakeSeq(categoryDataSource, kNC_SearchCategoryRoot, getter_AddRefs(categoryRoot));
	if (NS_FAILED(rv))	return(rv);
	if (!categoryRoot)	return(NS_ERROR_UNEXPECTED);

	rv = categoryRoot->Init(categoryDataSource, kNC_SearchCategoryRoot);
	if (NS_FAILED(rv))	return(rv);

	PRInt32		numCategories = 0;
	rv = categoryRoot->GetCount(&numCategories);
	if (NS_FAILED(rv))	return(rv);

	// Note: one-based
	for (PRInt32 catLoop=1; catLoop <= numCategories; catLoop++)
	{
		nsCOMPtr<nsIRDFResource>	aCategoryOrdinal;
		rv = gRDFC->IndexToOrdinalResource(catLoop,
			getter_AddRefs(aCategoryOrdinal));
		if (NS_FAILED(rv))	break;
		if (!aCategoryOrdinal)	break;

		nsCOMPtr<nsIRDFNode>		aCategoryNode;
		rv = categoryDataSource->GetTarget(kNC_SearchCategoryRoot, aCategoryOrdinal,
			PR_TRUE, getter_AddRefs(aCategoryNode));
		if (NS_FAILED(rv))	break;
		nsCOMPtr<nsIRDFResource> aCategoryRes (do_QueryInterface(aCategoryNode));
		if (!aCategoryRes)	break;
		const	char			*catResURI = nsnull;
		aCategoryRes->GetValueConst(&catResURI);
		if (!catResURI)		break;
		nsAutoString		categoryStr;
		categoryStr.AssignWithConversion(kURINC_SearchCategoryPrefix);
		categoryStr.AppendWithConversion(catResURI);

		nsCOMPtr<nsIRDFResource>	searchCategoryRes;
		if (NS_FAILED(rv = gRDFService->GetUnicodeResource(categoryStr,
			getter_AddRefs(searchCategoryRes))))	break;

		nsCOMPtr<nsIRDFContainer> categoryContainer;
		rv = gRDFC->MakeSeq(categoryDataSource, searchCategoryRes,
			getter_AddRefs(categoryContainer));
		if (NS_FAILED(rv))	continue;

		rv = categoryContainer->Init(categoryDataSource, searchCategoryRes);
		if (NS_FAILED(rv))	return(rv);

		PRInt32		numEngines = 0;
		rv = categoryContainer->GetCount(&numEngines);
		if (NS_FAILED(rv))	break;

		// Note: one-based, and loop backwards as we might be removing entries
		for (PRInt32 engineLoop=numEngines; engineLoop >= 1; engineLoop--)
		{
			nsCOMPtr<nsIRDFResource>	aEngineOrdinal;
			rv = gRDFC->IndexToOrdinalResource(engineLoop,
				getter_AddRefs(aEngineOrdinal));
			if (NS_FAILED(rv))	break;
			if (!aEngineOrdinal)	break;

			nsCOMPtr<nsIRDFNode>		aEngineNode;
			rv = categoryDataSource->GetTarget(searchCategoryRes, aEngineOrdinal,
				PR_TRUE, getter_AddRefs(aEngineNode));
			if (NS_FAILED(rv))	break;
			nsCOMPtr<nsIRDFResource> aEngineRes (do_QueryInterface(aEngineNode));
			if (!aEngineRes)	break;

			if (isSearchCategoryEngineURI(aEngineRes))
			{
				nsCOMPtr<nsIRDFResource>	trueEngine;
				rv = resolveSearchCategoryEngineURI(aEngineRes,
					getter_AddRefs(trueEngine));
				if (NS_FAILED(rv) || (!trueEngine))
				{
					// unable to resolve, so forget about this engine reference
#ifdef	DEBUG
					const	char		*catEngineURI = nsnull;
					aEngineRes->GetValueConst(&catEngineURI);
					if (catEngineURI)
					{
						printf("**** Stale search engine reference to '%s'\n",
							catEngineURI);
					}
#endif
					nsCOMPtr<nsIRDFNode>	staleCatEngine;
					rv = categoryContainer->RemoveElementAt(engineLoop, PR_TRUE,
						getter_AddRefs(staleCatEngine));
					isDirtyFlag = PR_TRUE;
				}
			}
		}
	}

  if (isDirtyFlag)
	{
		if (remoteCategoryDataSource)
		{
			remoteCategoryDataSource->Flush();
		}
	}

	return(rv);
}



NS_IMETHODIMP
InternetSearchDataSource::Assert(nsIRDFResource *source,
                       nsIRDFResource *property,
                       nsIRDFNode *target,
                       PRBool tv)
{
	nsresult	rv = NS_RDF_ASSERTION_REJECTED;

	// we only have positive assertions in the internet search data source.
	if (! tv)
		return(rv);

	if (mInner)
	{
		rv = mInner->Assert(source, property, target, tv);
	}
	return(rv);
}



NS_IMETHODIMP
InternetSearchDataSource::Unassert(nsIRDFResource *source,
                         nsIRDFResource *property,
                         nsIRDFNode *target)
{
	nsresult	rv = NS_RDF_ASSERTION_REJECTED;

	if (mInner)
	{
		rv = mInner->Unassert(source, property, target);
	}
	return(rv);
}



NS_IMETHODIMP
InternetSearchDataSource::Change(nsIRDFResource *source,
			nsIRDFResource *property,
			nsIRDFNode *oldTarget,
			nsIRDFNode *newTarget)
{
	nsresult	rv = NS_RDF_ASSERTION_REJECTED;

	if (mInner)
	{
		rv = mInner->Change(source, property, oldTarget, newTarget);
	}
	return(rv);
}



NS_IMETHODIMP
InternetSearchDataSource::Move(nsIRDFResource *oldSource,
					   nsIRDFResource *newSource,
					   nsIRDFResource *property,
					   nsIRDFNode *target)
{
	nsresult	rv = NS_RDF_ASSERTION_REJECTED;

	if (mInner)
	{
		rv = mInner->Move(oldSource, newSource, property, target);
	}
	return(rv);
}



NS_IMETHODIMP
InternetSearchDataSource::HasAssertion(nsIRDFResource *source,
                             nsIRDFResource *property,
                             nsIRDFNode *target,
                             PRBool tv,
                             PRBool *hasAssertion /* out */)
{
	NS_PRECONDITION(source != nsnull, "null ptr");
	if (! source)
		return NS_ERROR_NULL_POINTER;

	NS_PRECONDITION(property != nsnull, "null ptr");
	if (! property)
		return NS_ERROR_NULL_POINTER;

	NS_PRECONDITION(target != nsnull, "null ptr");
	if (! target)
		return NS_ERROR_NULL_POINTER;

	NS_PRECONDITION(hasAssertion != nsnull, "null ptr");
	if (! hasAssertion)
		return NS_ERROR_NULL_POINTER;

	*hasAssertion = PR_FALSE;

	// we only have positive assertions in the internet search data source.
	if (! tv)
	{
		return NS_OK;
        }
        nsresult	rv = NS_RDF_NO_VALUE;
        
        if (mInner)
        {
		rv = mInner->HasAssertion(source, property, target, tv, hasAssertion);
	}
        return(rv);
}

NS_IMETHODIMP 
InternetSearchDataSource::HasArcIn(nsIRDFNode *aNode, nsIRDFResource *aArc, PRBool *result)
{
    if (mInner) {
	return mInner->HasArcIn(aNode, aArc, result);
    }
    else {
	*result = PR_FALSE;
    }
    return NS_OK;
}

NS_IMETHODIMP 
InternetSearchDataSource::HasArcOut(nsIRDFResource *source, nsIRDFResource *aArc, PRBool *result)
{
    NS_PRECONDITION(source != nsnull, "null ptr");
    if (! source)
	return NS_ERROR_NULL_POINTER;

    nsresult rv;

    if ((source == kNC_SearchEngineRoot) || (source == kNC_LastSearchRoot) || isSearchURI(source))
    {
	*result = (aArc == kNC_Child);
	return NS_OK;
    }

    if ((isSearchCategoryURI(source)) && (categoryDataSource))
    {
	const char	*uri = nsnull;
	source->GetValueConst(&uri);
	if (!uri)	return(NS_ERROR_UNEXPECTED);

	nsCOMPtr<nsIRDFResource>	category;
	if (NS_FAILED(rv = gRDFService->GetResource(nsDependentCString(uri),
						    getter_AddRefs(category))))
	    return(rv);

	return categoryDataSource->HasArcOut(source, aArc, result);
    }

    if (isSearchCategoryEngineURI(source))
    {
	nsCOMPtr<nsIRDFResource>	trueEngine;
	rv = resolveSearchCategoryEngineURI(source, getter_AddRefs(trueEngine));
	if (NS_FAILED(rv) || (rv == NS_RDF_NO_VALUE))	return(rv);
	if (!trueEngine) {
	    *result = PR_FALSE;
	    return NS_OK;
	}
	source = trueEngine;
    }

    if (isEngineURI(source))
    {
	// if we're asking for info on a search engine, (deferred) load it if needed
	nsCOMPtr<nsIRDFLiteral>	dataLit;
	FindData(source, getter_AddRefs(dataLit));
    }

    if (mInner)
    {
	return mInner->HasArcOut(source, aArc, result);
    }

    *result = PR_FALSE;
    return NS_OK;
}

NS_IMETHODIMP
InternetSearchDataSource::ArcLabelsIn(nsIRDFNode *node,
                            nsISimpleEnumerator ** labels /* out */)
{
	nsresult	rv;

	if (mInner)
	{
		rv = mInner->ArcLabelsIn(node, labels);
		return(rv);
	}
	else
	{
		rv = NS_NewEmptyEnumerator(labels);
	}
	return(rv);
}



NS_IMETHODIMP
InternetSearchDataSource::ArcLabelsOut(nsIRDFResource *source,
                             nsISimpleEnumerator **labels /* out */)
{
	NS_PRECONDITION(source != nsnull, "null ptr");
	if (! source)
		return NS_ERROR_NULL_POINTER;

	NS_PRECONDITION(labels != nsnull, "null ptr");
	if (! labels)
		return NS_ERROR_NULL_POINTER;

	nsresult rv;

	if ((source == kNC_SearchEngineRoot) || (source == kNC_LastSearchRoot) || isSearchURI(source))
	{
            nsCOMPtr<nsISupportsArray> array;
            rv = NS_NewISupportsArray(getter_AddRefs(array));
            if (NS_FAILED(rv)) return rv;

            array->AppendElement(kNC_Child);

            nsISimpleEnumerator* result = new nsArrayEnumerator(array);
            if (! result)
                return NS_ERROR_OUT_OF_MEMORY;

            NS_ADDREF(result);
            *labels = result;
            return(NS_OK);
	}

	if ((isSearchCategoryURI(source)) && (categoryDataSource))
	{
		const char	*uri = nsnull;
		source->GetValueConst(&uri);
		if (!uri)	return(NS_ERROR_UNEXPECTED);

		nsCOMPtr<nsIRDFResource>	category;
		if (NS_FAILED(rv = gRDFService->GetResource(nsDependentCString(uri),
			getter_AddRefs(category))))
			return(rv);

		rv = categoryDataSource->ArcLabelsOut(category, labels);
		return(rv);
	}

	if (isSearchCategoryEngineURI(source))
	{
		nsCOMPtr<nsIRDFResource>	trueEngine;
		rv = resolveSearchCategoryEngineURI(source, getter_AddRefs(trueEngine));
		if (NS_FAILED(rv) || (rv == NS_RDF_NO_VALUE))	return(rv);
		if (!trueEngine)	return(NS_RDF_NO_VALUE);

		source = trueEngine;
	}

	if (isEngineURI(source))
	{
		// if we're asking for info on a search engine, (deferred) load it if needed
		nsCOMPtr<nsIRDFLiteral>	dataLit;
		FindData(source, getter_AddRefs(dataLit));
	}

	if (mInner)
	{
		rv = mInner->ArcLabelsOut(source, labels);
		return(rv);
	}

	return NS_NewEmptyEnumerator(labels);
}



NS_IMETHODIMP
InternetSearchDataSource::GetAllResources(nsISimpleEnumerator** aCursor)
{
	nsresult	rv = NS_RDF_NO_VALUE;

	if (mInner)
	{
		rv = mInner->GetAllResources(aCursor);
	}
	return(rv);
}



NS_IMETHODIMP
InternetSearchDataSource::AddObserver(nsIRDFObserver *aObserver)
{
	nsresult	rv = NS_OK;

	if (mInner)
	{
		rv = mInner->AddObserver(aObserver);
	}
	return(rv);
}



NS_IMETHODIMP
InternetSearchDataSource::RemoveObserver(nsIRDFObserver *aObserver)
{
	nsresult	rv = NS_OK;

	if (mInner)
	{
		rv = mInner->RemoveObserver(aObserver);
	}
	return(rv);
}



NS_IMETHODIMP
InternetSearchDataSource::GetAllCmds(nsIRDFResource* source,
                                     nsISimpleEnumerator/*<nsIRDFResource>*/** commands)
{
	nsCOMPtr<nsISupportsArray>	cmdArray;
	nsresult			rv;
	rv = NS_NewISupportsArray(getter_AddRefs(cmdArray));
	if (NS_FAILED(rv))	return(rv);

	// check if we have any filters, enable command to clear them
	PRBool				haveFilters = PR_FALSE;

	if (mLocalstore)
	{
		nsCOMPtr<nsISimpleEnumerator>	cursor;
		PRBool				hasMore = PR_FALSE;

		// check kNC_FilterSearchURLsRoot
		if (NS_SUCCEEDED(rv = mLocalstore->GetTargets(kNC_FilterSearchURLsRoot, kNC_Child,
			PR_TRUE, getter_AddRefs(cursor))))
		{
      if (NS_SUCCEEDED(cursor->HasMoreElements(&hasMore)) && hasMore)
			{
				haveFilters = PR_TRUE;
			}
		}
    if (!haveFilters)
		{
			// check kNC_FilterSearchSitesRoot
			if (NS_SUCCEEDED(rv = mLocalstore->GetTargets(kNC_FilterSearchSitesRoot, kNC_Child,
				PR_TRUE, getter_AddRefs(cursor))))
			{
        if (NS_SUCCEEDED(cursor->HasMoreElements(&hasMore)) && hasMore)
				{
					haveFilters = PR_TRUE;
				}
			}
		}
	}

	PRBool				isSearchResult = PR_FALSE;
  rv = mInner->HasAssertion(source, kRDF_type, kNC_SearchResult, PR_TRUE,
          &isSearchResult);
  if (NS_SUCCEEDED(rv) && isSearchResult)
	{
#ifndef MOZ_PLACES
		nsCOMPtr<nsIRDFDataSource>	datasource;
		if (NS_SUCCEEDED(rv = gRDFService->GetDataSource("rdf:bookmarks", getter_AddRefs(datasource))))
		{
			nsCOMPtr<nsIBookmarksService> bookmarks (do_QueryInterface(datasource));
			if (bookmarks)
			{
				char *uri = getSearchURI(source);
				if (uri)
				{
					PRBool	isBookmarkedFlag = PR_FALSE;
          rv = bookmarks->IsBookmarked(uri, &isBookmarkedFlag);
          if (NS_SUCCEEDED(rv) && !isBookmarkedFlag)
					{
						cmdArray->AppendElement(kNC_SearchCommand_AddToBookmarks);
					}
					Recycle(uri);
				}
			}
		}
#endif
		cmdArray->AppendElement(kNC_SearchCommand_AddQueryToBookmarks);
		cmdArray->AppendElement(kNC_BookmarkSeparator);

		// if this is a search result, and it isn't filtered, enable command to be able to filter it
		PRBool				isURLFiltered = PR_FALSE;
    rv = mInner->HasAssertion(kNC_FilterSearchURLsRoot, kNC_Child, source,
                              PR_TRUE, &isURLFiltered);
    if (NS_SUCCEEDED(rv) && !isURLFiltered)
		{
			cmdArray->AppendElement(kNC_SearchCommand_FilterResult);
		}

		// XXX (should check site) if this is a search result site, and the site isn't filtered,
		//     enable command to filter it
		cmdArray->AppendElement(kNC_SearchCommand_FilterSite);

    if (haveFilters)
		{
			cmdArray->AppendElement(kNC_BookmarkSeparator);
			cmdArray->AppendElement(kNC_SearchCommand_ClearFilters);
		}
	}
	else if (isSearchURI(source) || (source == kNC_LastSearchRoot))
	{
    if (haveFilters)
		{
			cmdArray->AppendElement(kNC_SearchCommand_ClearFilters);
		}
	}

	// always append a separator last (due to aggregation of commands from multiple datasources)
	cmdArray->AppendElement(kNC_BookmarkSeparator);

	nsISimpleEnumerator		*result = new nsArrayEnumerator(cmdArray);
	if (!result)
		return(NS_ERROR_OUT_OF_MEMORY);
	NS_ADDREF(result);
	*commands = result;
	return(NS_OK);
}



NS_IMETHODIMP
InternetSearchDataSource::IsCommandEnabled(nsISupportsArray/*<nsIRDFResource>*/* aSources,
                                       nsIRDFResource*   aCommand,
                                       nsISupportsArray/*<nsIRDFResource>*/* aArguments,
                                       PRBool* aResult)
{
	return(NS_ERROR_NOT_IMPLEMENTED);
}



char *
InternetSearchDataSource::getSearchURI(nsIRDFResource *src)
{
	char	*uri = nsnull;

	if (src)
	{
		nsresult		rv;
		nsCOMPtr<nsIRDFNode>	srcNode;
		if (NS_SUCCEEDED(rv = mInner->GetTarget(src, kNC_URL, PR_TRUE, getter_AddRefs(srcNode))))
		{
			nsCOMPtr<nsIRDFLiteral>	urlLiteral (do_QueryInterface(srcNode));
			if (urlLiteral)
			{
				const PRUnichar	*uriUni = nsnull;
				urlLiteral->GetValueConst(&uriUni);
				if (uriUni)
				{
					nsAutoString	uriString(uriUni);
					uri = ToNewUTF8String(uriString);
				}
			}
		}
	}
	return(uri);
}



nsresult
InternetSearchDataSource::addToBookmarks(nsIRDFResource *src)
{
	if (!src)	return(NS_ERROR_UNEXPECTED);
	if (!mInner)	return(NS_ERROR_UNEXPECTED);

	nsresult		rv;
	nsCOMPtr<nsIRDFNode>	nameNode;
	// Note: nameLiteral needs to stay in scope for the life of "name"
	nsCOMPtr<nsIRDFLiteral>	nameLiteral;
	const PRUnichar		*name = nsnull;
	if (NS_SUCCEEDED(rv = mInner->GetTarget(src, kNC_Name, PR_TRUE, getter_AddRefs(nameNode))))
	{
		nameLiteral = do_QueryInterface(nameNode);
		if (nameLiteral)
		{
			nameLiteral->GetValueConst(&name);
		}
	}

#ifndef MOZ_PLACES
	nsCOMPtr<nsIRDFDataSource>	datasource;
	if (NS_SUCCEEDED(rv = gRDFService->GetDataSource("rdf:bookmarks", getter_AddRefs(datasource))))
	{
		nsCOMPtr<nsIBookmarksService> bookmarks (do_QueryInterface(datasource));
		if (bookmarks)
		{
			char	*uri = getSearchURI(src);
			if (uri)
			{
				rv = bookmarks->AddBookmarkImmediately(NS_ConvertUTF8toUTF16(uri).get(),
                                               name, nsIBookmarksService::BOOKMARK_SEARCH_TYPE, nsnull);
				Recycle(uri);
			}
		}
	}
#endif

	return(NS_OK);
}



nsresult
InternetSearchDataSource::addQueryToBookmarks(nsIRDFResource *src)
{
	if (!src)	return(NS_ERROR_UNEXPECTED);
	if (!mInner)	return(NS_ERROR_UNEXPECTED);

	nsresult rv;
	nsCOMPtr<nsIRDFNode>	refNode;
	if (NS_FAILED(rv = mInner->GetTarget(kNC_LastSearchRoot, kNC_Ref, PR_TRUE,
		getter_AddRefs(refNode))))
		return(rv);
	nsCOMPtr<nsIRDFLiteral>	urlLiteral (do_QueryInterface(refNode));
	if (!urlLiteral)
	  return(NS_ERROR_UNEXPECTED);
	const PRUnichar	*uriUni = nsnull;
	urlLiteral->GetValueConst(&uriUni);

	nsCOMPtr<nsIRDFNode>	textNode;
	if (NS_FAILED(rv = mInner->GetTarget(kNC_LastSearchRoot, kNC_LastText, PR_TRUE,
		getter_AddRefs(textNode))))
		return(rv);
	nsCOMPtr<nsIRDFLiteral> textLiteral = do_QueryInterface(textNode);
	nsXPIDLString value;
	if (textLiteral)
	{
		const PRUnichar *textUni = nsnull;
		textLiteral->GetValueConst(&textUni);
  	nsAutoString name;
		name.Assign(textUni);
		// replace pluses with spaces
		name.ReplaceChar(PRUnichar('+'), PRUnichar(' '));

  	nsCOMPtr<nsIStringBundleService>
  	stringService(do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv));
  	if (NS_SUCCEEDED(rv) && stringService)
  	{
  		nsCOMPtr<nsIStringBundle> bundle;
  		rv = stringService->CreateBundle(SEARCH_PROPERTIES, getter_AddRefs(bundle));
  		if (bundle)
  		{
  			const PRUnichar *strings[] = { name.get() };
  			rv = bundle->FormatStringFromName(NS_LITERAL_STRING("searchTitle").get(), strings, 1, 
  				getter_Copies(value));
			}
		}
	}

#ifndef MOZ_PLACES
	nsCOMPtr<nsIRDFDataSource>	datasource;
	if (NS_SUCCEEDED(rv = gRDFService->GetDataSource("rdf:bookmarks", getter_AddRefs(datasource))))
	{
		nsCOMPtr<nsIBookmarksService> bookmarks (do_QueryInterface(datasource));
		if (bookmarks)
			rv = bookmarks->AddBookmarkImmediately(uriUni, value.get(),
                                             nsIBookmarksService::BOOKMARK_SEARCH_TYPE, nsnull);
	}
#endif

	return(NS_OK);
}



nsresult
InternetSearchDataSource::filterResult(nsIRDFResource *aResource)
{
	if (!aResource)	return(NS_ERROR_UNEXPECTED);
	if (!mInner)	return(NS_ERROR_UNEXPECTED);

	// remove all anonymous resources which have this as a #URL
	char	*uri = getSearchURI(aResource);
	if (!uri)	return(NS_ERROR_UNEXPECTED);
	nsAutoString	url;
	url.AssignWithConversion(uri);
	Recycle(uri);

	nsresult			rv;
	nsCOMPtr<nsIRDFLiteral>	urlLiteral;
	if (NS_FAILED(rv = gRDFService->GetLiteral(url.get(), getter_AddRefs(urlLiteral)))
		|| (urlLiteral == nsnull))	return(NS_ERROR_UNEXPECTED);

	// add aResource into a list of nodes to filter out
	PRBool	alreadyFiltered = PR_FALSE;
  rv = mLocalstore->HasAssertion(kNC_FilterSearchURLsRoot, kNC_Child,
         urlLiteral, PR_TRUE, &alreadyFiltered);
  if (NS_SUCCEEDED(rv) && alreadyFiltered)
	{
		// already filtered, nothing to do
		return(rv);
	}

	// filter this URL out
	mLocalstore->Assert(kNC_FilterSearchURLsRoot, kNC_Child, urlLiteral, PR_TRUE);

	// flush localstore
	nsCOMPtr<nsIRDFRemoteDataSource> remoteLocalStore (do_QueryInterface(mLocalstore));
	if (remoteLocalStore)
	{
		remoteLocalStore->Flush();
	}

	nsCOMPtr<nsISimpleEnumerator>	anonArcs;
	if (NS_SUCCEEDED(rv = mInner->GetSources(kNC_URL, urlLiteral, PR_TRUE,
		getter_AddRefs(anonArcs))))
	{
		PRBool			hasMoreAnonArcs = PR_TRUE;
    while (hasMoreAnonArcs)
		{
      if (NS_FAILED(anonArcs->HasMoreElements(&hasMoreAnonArcs)) ||
          !hasMoreAnonArcs)
        break;
			nsCOMPtr<nsISupports>	anonArc;
			if (NS_FAILED(anonArcs->GetNext(getter_AddRefs(anonArc))))
				break;
			nsCOMPtr<nsIRDFResource> anonChild (do_QueryInterface(anonArc));
			if (!anonChild)	continue;

			PRBool	isSearchResult = PR_FALSE;
      rv = mInner->HasAssertion(anonChild, kRDF_type, kNC_SearchResult,
                                PR_TRUE, &isSearchResult);
      if (NS_FAILED(rv) || !isSearchResult)
				continue;

			nsCOMPtr<nsIRDFResource>	anonParent;
			if (NS_FAILED(rv = mInner->GetSource(kNC_Child, anonChild, PR_TRUE,
				getter_AddRefs(anonParent))))	continue;
			if (!anonParent)	continue;

			mInner->Unassert(anonParent, kNC_Child, anonChild);
		}
	}

	return(NS_OK);
}



nsresult
InternetSearchDataSource::filterSite(nsIRDFResource *aResource)
{
	if (!aResource)	return(NS_ERROR_UNEXPECTED);
	if (!mInner)	return(NS_ERROR_UNEXPECTED);

	char	*uri = getSearchURI(aResource);
	if (!uri)	return(NS_ERROR_UNEXPECTED);
	nsAutoString	host;
	host.AssignWithConversion(uri);
	Recycle(uri);

	// determine site (host name)
	PRInt32		slashOffset1 = host.Find("://");
	if (slashOffset1 < 1)			return(NS_ERROR_UNEXPECTED);
	PRInt32 	slashOffset2 = host.FindChar(PRUnichar('/'), slashOffset1 + 3);
	if (slashOffset2 <= slashOffset1)	return(NS_ERROR_UNEXPECTED);
	host.Truncate(slashOffset2 + 1);

	nsresult			rv;
	nsCOMPtr<nsIRDFLiteral>	urlLiteral;
	if (NS_FAILED(rv = gRDFService->GetLiteral(host.get(), getter_AddRefs(urlLiteral)))
		|| (urlLiteral == nsnull))	return(NS_ERROR_UNEXPECTED);

	// add aResource into a list of nodes to filter out
	PRBool	alreadyFiltered = PR_FALSE;
  rv = mLocalstore->HasAssertion(kNC_FilterSearchSitesRoot, kNC_Child,
         urlLiteral, PR_TRUE, &alreadyFiltered);
  if (NS_SUCCEEDED(rv) && alreadyFiltered)
	{
		// already filtered, nothing to do
		return(rv);
	}

	// filter this site out
	mLocalstore->Assert(kNC_FilterSearchSitesRoot, kNC_Child, urlLiteral, PR_TRUE);

	// flush localstore
	nsCOMPtr<nsIRDFRemoteDataSource> remoteLocalStore (do_QueryInterface(mLocalstore));
	if (remoteLocalStore)
	{
		remoteLocalStore->Flush();
	}

	// remove all anonymous resources which have this as a site

	nsCOMPtr<nsISupportsArray>	array;
	nsCOMPtr<nsIRDFResource>	aRes;
	nsCOMPtr<nsISimpleEnumerator>	cursor;
	PRBool				hasMore;

	rv = NS_NewISupportsArray(getter_AddRefs(array));
	if (NS_FAILED(rv)) return rv;

	if (NS_FAILED(rv = GetAllResources(getter_AddRefs(cursor))))	return(rv);
	if (!cursor)	return(NS_ERROR_UNEXPECTED);

	hasMore = PR_TRUE;
  while (hasMore)
	{
		if (NS_FAILED(rv = cursor->HasMoreElements(&hasMore)))	return(rv);
    if (!hasMore)
      break;
		
		nsCOMPtr<nsISupports>	isupports;
		if (NS_FAILED(rv = cursor->GetNext(getter_AddRefs(isupports))))
				return(rv);
		if (!isupports)	return(NS_ERROR_UNEXPECTED);
		aRes = do_QueryInterface(isupports);
		if (!aRes)	return(NS_ERROR_UNEXPECTED);

		if ((aRes.get() == kNC_LastSearchRoot) || (isSearchURI(aRes)))
		{
			array->AppendElement(aRes);
		}
	}

	PRUint32	count;
	if (NS_FAILED(rv = array->Count(&count)))	return(rv);
	for (PRUint32 loop=0; loop<count; loop++)
	{
		nsCOMPtr<nsIRDFResource> aSearchRoot (do_QueryElementAt(array, loop));
		if (!aSearchRoot)	break;

		if (NS_SUCCEEDED(rv = mInner->GetTargets(aSearchRoot, kNC_Child,
			PR_TRUE, getter_AddRefs(cursor))))
		{
			hasMore = PR_TRUE;
      while (hasMore)
			{
        if (NS_FAILED(cursor->HasMoreElements(&hasMore)) || !hasMore)
          break;

				nsCOMPtr<nsISupports>		arc;
				if (NS_FAILED(cursor->GetNext(getter_AddRefs(arc))))
					break;
				aRes = do_QueryInterface(arc);
				if (!aRes)	break;

				uri = getSearchURI(aRes);
				if (!uri)	return(NS_ERROR_UNEXPECTED);
				nsAutoString	site;
				site.AssignWithConversion(uri);
				Recycle(uri);

				// determine site (host name)
				slashOffset1 = site.Find("://");
				if (slashOffset1 < 1)			return(NS_ERROR_UNEXPECTED);
				slashOffset2 = site.FindChar(PRUnichar('/'), slashOffset1 + 3);
				if (slashOffset2 <= slashOffset1)	return(NS_ERROR_UNEXPECTED);
				site.Truncate(slashOffset2 + 1);

				if (site.Equals(host, nsCaseInsensitiveStringComparator()))
				{
					mInner->Unassert(aSearchRoot, kNC_Child, aRes);
				}
			}
		}
	}

	return(NS_OK);
}



nsresult
InternetSearchDataSource::clearFilters(void)
{
	if (!mInner)	return(NS_ERROR_UNEXPECTED);

	nsresult			rv;
	nsCOMPtr<nsISimpleEnumerator>	arcs;
	PRBool				hasMore = PR_TRUE;
	nsCOMPtr<nsISupports>		arc;

	// remove all filtered URLs
	if (NS_SUCCEEDED(rv = mLocalstore->GetTargets(kNC_FilterSearchURLsRoot, kNC_Child,
		PR_TRUE, getter_AddRefs(arcs))))
	{
		hasMore = PR_TRUE;
    while (hasMore)
		{
      if (NS_FAILED(arcs->HasMoreElements(&hasMore)) || !hasMore)
				break;
			if (NS_FAILED(arcs->GetNext(getter_AddRefs(arc))))
				break;

			nsCOMPtr<nsIRDFLiteral>	filterURL (do_QueryInterface(arc));
			if (!filterURL)	continue;
			
			mLocalstore->Unassert(kNC_FilterSearchURLsRoot, kNC_Child, filterURL);
		}
	}

	// remove all filtered sites
	if (NS_SUCCEEDED(rv = mLocalstore->GetTargets(kNC_FilterSearchSitesRoot, kNC_Child,
		PR_TRUE, getter_AddRefs(arcs))))
	{
		hasMore = PR_TRUE;
    while (hasMore)
		{
      if (NS_FAILED(arcs->HasMoreElements(&hasMore)) || !hasMore)
				break;
			if (NS_FAILED(arcs->GetNext(getter_AddRefs(arc))))
				break;

			nsCOMPtr<nsIRDFLiteral>	filterSiteLiteral (do_QueryInterface(arc));
			if (!filterSiteLiteral)	continue;
			
			mLocalstore->Unassert(kNC_FilterSearchSitesRoot, kNC_Child, filterSiteLiteral);
		}
	}

	// flush localstore
	nsCOMPtr<nsIRDFRemoteDataSource> remoteLocalStore (do_QueryInterface(mLocalstore));
	if (remoteLocalStore)
	{
		remoteLocalStore->Flush();
	}

	return(NS_OK);
}



PRBool
InternetSearchDataSource::isSearchResultFiltered(const nsString &hrefStr)
{
	PRBool		filterStatus = PR_FALSE;
	nsresult	rv;

	const PRUnichar	*hrefUni = hrefStr.get();
	if (!hrefUni)	return(filterStatus);

	// check if this specific URL is to be filtered out
	nsCOMPtr<nsIRDFLiteral>	hrefLiteral;
	if (NS_SUCCEEDED(rv = gRDFService->GetLiteral(hrefUni, getter_AddRefs(hrefLiteral))))
	{
		if (NS_SUCCEEDED(rv = mLocalstore->HasAssertion(kNC_FilterSearchURLsRoot,
			kNC_Child, hrefLiteral, PR_TRUE, &filterStatus)))
		{
      if (filterStatus)
			{
				return(filterStatus);
			}
		}
	}

	// check if this specific Site is to be filtered out

	// determine site (host name)
	nsAutoString	host(hrefStr);
	PRInt32		slashOffset1 = host.Find("://");
	if (slashOffset1 < 1)			return(NS_ERROR_UNEXPECTED);
	PRInt32 	slashOffset2 = host.FindChar(PRUnichar('/'), slashOffset1 + 3);
	if (slashOffset2 <= slashOffset1)	return(NS_ERROR_UNEXPECTED);
	host.Truncate(slashOffset2 + 1);

	nsCOMPtr<nsIRDFLiteral>	urlLiteral;
	if (NS_FAILED(rv = gRDFService->GetLiteral(host.get(), getter_AddRefs(urlLiteral)))
		|| (urlLiteral == nsnull))	return(NS_ERROR_UNEXPECTED);

	rv = mLocalstore->HasAssertion(kNC_FilterSearchSitesRoot, kNC_Child, urlLiteral,
		PR_TRUE, &filterStatus);

	return(filterStatus);
}



NS_IMETHODIMP
InternetSearchDataSource::DoCommand(nsISupportsArray/*<nsIRDFResource>*/* aSources,
                                nsIRDFResource*   aCommand,
                                nsISupportsArray/*<nsIRDFResource>*/* aArguments)
{
	nsresult		rv = NS_OK;
	PRInt32			loop;
	PRUint32		numSources;
	if (NS_FAILED(rv = aSources->Count(&numSources)))	return(rv);
	if (numSources < 1)
	{
		return(NS_ERROR_ILLEGAL_VALUE);
	}

	for (loop=((PRInt32)numSources)-1; loop>=0; loop--)
	{
		nsCOMPtr<nsIRDFResource> src (do_QueryElementAt(aSources, loop));
		if (!src)	return(NS_ERROR_NO_INTERFACE);

		if (aCommand == kNC_SearchCommand_AddToBookmarks)
		{
			if (NS_FAILED(rv = addToBookmarks(src)))
				return(rv);
		}
		else if (aCommand == kNC_SearchCommand_AddQueryToBookmarks)
		{
		  if (NS_FAILED(rv = addQueryToBookmarks(src)))
		    return(rv);
		}
		else if (aCommand == kNC_SearchCommand_FilterResult)
		{
			if (NS_FAILED(rv = filterResult(src)))
				return(rv);
		}
		else if (aCommand == kNC_SearchCommand_FilterSite)
		{
			if (NS_FAILED(rv = filterSite(src)))
				return(rv);
		}
		else if (aCommand == kNC_SearchCommand_ClearFilters)
		{
			if (NS_FAILED(rv = clearFilters()))
				return(rv);
		}
	}
	return(NS_OK);
}

NS_IMETHODIMP
InternetSearchDataSource::BeginUpdateBatch()
{
        return mInner->BeginUpdateBatch();
}

NS_IMETHODIMP
InternetSearchDataSource::EndUpdateBatch()
{
        return mInner->EndUpdateBatch();
}

NS_IMETHODIMP
InternetSearchDataSource::AddSearchEngine(const char *engineURL, const char *iconURL,
					  const PRUnichar *suggestedTitle, const PRUnichar *suggestedCategory)
{
        return AddSearchEngineInternal(engineURL, iconURL, suggestedTitle,
                                       suggestedCategory, nsnull);
}

nsresult
InternetSearchDataSource::AddSearchEngineInternal(const char *engineURL, const char *iconURL,
                                                  const PRUnichar *suggestedTitle,
                                                  const PRUnichar *suggestedCategory,
                                                  nsIRDFResource *aOldEngineResource)
{
	NS_PRECONDITION(engineURL != nsnull, "null ptr");
	if (!engineURL)	return(NS_ERROR_NULL_POINTER);
	// Note: iconURL, suggestedTitle & suggestedCategory
	// can be null or empty strings, which is OK

#ifdef	DEBUG_SEARCH_OUTPUT
	printf("AddSearchEngine: engine='%s'\n", engineURL);
#endif

	nsresult	rv = NS_OK;

	// mBackgroundLoadGroup is a dynamically created singleton
	if (!mBackgroundLoadGroup)
	{
		if (NS_FAILED(rv = NS_NewLoadGroup(getter_AddRefs(mBackgroundLoadGroup), nsnull)))
			return(rv);
		if (!mBackgroundLoadGroup)
			return(NS_ERROR_UNEXPECTED);
	}

	// download engine
	nsCOMPtr<nsIInternetSearchContext>	engineContext;
  rv = NS_NewInternetSearchContext
         (aOldEngineResource? nsIInternetSearchContext::ENGINE_DOWNLOAD_UPDATE_CONTEXT:
                              nsIInternetSearchContext::ENGINE_DOWNLOAD_NEW_CONTEXT,
          nsnull, aOldEngineResource, nsnull, suggestedCategory,
          getter_AddRefs(engineContext));
  NS_ENSURE_SUCCESS(rv, rv);

	if (!engineContext)	return(NS_ERROR_UNEXPECTED);

	nsCOMPtr<nsIURI>	engineURI;
	if (NS_FAILED(rv = NS_NewURI(getter_AddRefs(engineURI), engineURL)))
		return(rv);

	nsCOMPtr<nsIChannel>	engineChannel;
	if (NS_FAILED(rv = NS_NewChannel(getter_AddRefs(engineChannel), engineURI, nsnull, mBackgroundLoadGroup)))
		return(rv);
    
	if (NS_FAILED(rv = engineChannel->AsyncOpen(this, engineContext)))
		return(rv);

	// download icon
	nsCOMPtr<nsIInternetSearchContext>	iconContext;
  rv = NS_NewInternetSearchContext
         (aOldEngineResource? nsIInternetSearchContext::ICON_DOWNLOAD_UPDATE_CONTEXT:
                              nsIInternetSearchContext::ICON_DOWNLOAD_NEW_CONTEXT,
          nsnull, aOldEngineResource, nsnull, suggestedCategory,
          getter_AddRefs(iconContext));
  NS_ENSURE_SUCCESS(rv, rv);

	if (!iconContext)	return(NS_ERROR_UNEXPECTED);

	if (iconURL && (*iconURL))
	{
		nsCOMPtr<nsIURI>	iconURI;
		if (NS_FAILED(rv = NS_NewURI(getter_AddRefs(iconURI), iconURL)))
			return(rv);

		nsCOMPtr<nsIChannel>	iconChannel;
		if (NS_FAILED(rv = NS_NewChannel(getter_AddRefs(iconChannel), iconURI, nsnull, mBackgroundLoadGroup)))
			return(rv);
		if (NS_FAILED(rv = iconChannel->AsyncOpen(this, iconContext)))
			return(rv);
	}
	return(NS_OK);
}



nsresult
InternetSearchDataSource::saveContents(nsIChannel* channel, nsIInternetSearchContext *context, PRUint32	contextType)
{
    NS_ASSERTION(contextType == nsIInternetSearchContext::ENGINE_DOWNLOAD_NEW_CONTEXT ||
                 contextType == nsIInternetSearchContext::ICON_DOWNLOAD_NEW_CONTEXT ||
                 contextType == nsIInternetSearchContext::ENGINE_DOWNLOAD_UPDATE_CONTEXT ||
                 contextType == nsIInternetSearchContext::ICON_DOWNLOAD_UPDATE_CONTEXT,
                 "unexpected context");
	nsresult	rv = NS_OK;

	if (!channel)	return(NS_ERROR_UNEXPECTED);
	if (!context)	return(NS_ERROR_UNEXPECTED);

	// get real URI
	nsCOMPtr<nsIURI>	channelURI;
	if (NS_FAILED(rv = channel->GetURI(getter_AddRefs(channelURI))))
		return(rv);
	if (!channelURI)
		return(NS_ERROR_NULL_POINTER);

	nsCAutoString baseName;
	if (NS_FAILED(rv = channelURI->GetSpec(baseName)))
		return(rv);

	PRInt32			slashOffset = baseName.RFindChar(PRUnichar('/'));
	if (slashOffset < 0)		return(NS_ERROR_UNEXPECTED);
	baseName.Cut(0, slashOffset+1);
	if (baseName.IsEmpty())	return(NS_ERROR_UNEXPECTED);

	// make sure that search engines are .src files
	PRInt32	extensionOffset;
	if (contextType == nsIInternetSearchContext::ENGINE_DOWNLOAD_NEW_CONTEXT ||
		contextType == nsIInternetSearchContext::ENGINE_DOWNLOAD_UPDATE_CONTEXT)
	{
		extensionOffset = baseName.RFind(".src", PR_TRUE);
		if ((extensionOffset < 0) || (extensionOffset != (PRInt32)(baseName.Length()-4)))
		{
			return(NS_ERROR_UNEXPECTED);
		}
	}

    nsCOMPtr<nsIFile> outFile;
  // If the mode is "UPDATE", the output dir is same as the original file
  // location. Otherwise ("NEW" mode), located in NS_APP_USER_SEARCH_DIR.
  nsCOMPtr<nsIRDFResource> oldResource;
  rv = context->GetEngine(getter_AddRefs(oldResource));

  if (oldResource) {
    nsCOMPtr<nsILocalFile> oldEngineFile;
    rv = EngineFileFromResource(oldResource, getter_AddRefs(oldEngineFile));
    NS_ENSURE_SUCCESS(rv, rv);

    rv = oldEngineFile->GetParent(getter_AddRefs(outFile));
    NS_ENSURE_SUCCESS(rv, rv);
  }
  else {
    rv = NS_GetSpecialDirectory(NS_APP_USER_SEARCH_DIR, getter_AddRefs(outFile));
    if (NS_FAILED(rv))
        return rv;
  }

    PRBool exists;
    rv = outFile->Exists(&exists);
    if (NS_FAILED(rv)) return(rv);
    if (!exists)
    {
        rv = outFile->Create(nsIFile::DIRECTORY_TYPE, 0755);
        if (NS_FAILED(rv)) return(rv);
    }

	const PRUnichar	*dataBuf = nsnull;
	if (NS_FAILED(rv = context->GetBufferConst(&dataBuf)))	return(rv);

	// if no data, then nothing to do
	// Note: do this before opening file, as it would be truncated
	PRInt32		bufferLength = 0;
	if (NS_FAILED(context->GetBufferLength(&bufferLength)))	return(rv);
	if (bufferLength < 1)	return(NS_OK);
	
	rv = outFile->Append(NS_ConvertUTF8toUCS2(baseName));
	if (NS_FAILED(rv)) return rv;
	
	// save data to file
	// Note: write out one character at a time, as we might be dealing
	//       with binary data (such as 0x00) [especially for images]
    //
    // XXX - It appears that this is done in order to discard the upper
    // byte of each PRUnichar.  I hope that's OK!!
    //
    if (contextType == nsIInternetSearchContext::ENGINE_DOWNLOAD_UPDATE_CONTEXT ||
        contextType == nsIInternetSearchContext::ICON_DOWNLOAD_UPDATE_CONTEXT) {
        // This is an update operation that we triggered.  Remove the old one.
        outFile->Remove(PR_FALSE);
    } else {
        PRBool exists;
        rv = outFile->Exists(&exists);
        if (NS_FAILED(rv) || exists) {
            // We already have a search plugin with this filename; don't
            // replace it (bug 290038).
            return NS_ERROR_UNEXPECTED;
        }
    }

    nsCOMPtr<nsIOutputStream> outputStream, fileOutputStream;
    rv = NS_NewLocalFileOutputStream(getter_AddRefs(fileOutputStream), outFile);
    if (NS_FAILED(rv)) return rv;
    rv = NS_NewBufferedOutputStream(getter_AddRefs(outputStream), fileOutputStream, 4096);
    if (NS_FAILED(rv)) return rv;

    PRUint32 bytesWritten;
    for (PRInt32 loop=0; loop < bufferLength; loop++)
    {
        const char b = (const char) dataBuf[loop];
        outputStream->Write(&b, 1, &bytesWritten);
    }
    outputStream->Flush();		
    outputStream->Close();

    if (contextType == nsIInternetSearchContext::ENGINE_DOWNLOAD_NEW_CONTEXT ||
        contextType == nsIInternetSearchContext::ENGINE_DOWNLOAD_UPDATE_CONTEXT)
    {
        // check suggested category hint
        const PRUnichar	*hintUni = nsnull;
        rv = context->GetHintConst(&hintUni);

        // update graph with various required info
        SaveEngineInfoIntoGraph(outFile, nsnull, hintUni, dataBuf, PR_FALSE);
    }
    else if (contextType == nsIInternetSearchContext::ICON_DOWNLOAD_NEW_CONTEXT ||
             contextType == nsIInternetSearchContext::ICON_DOWNLOAD_UPDATE_CONTEXT)
    {
        // update graph with icon info
        SaveEngineInfoIntoGraph(nsnull, outFile, nsnull, nsnull, PR_FALSE);
    }

	// after we're all done with the data buffer, get rid of it
	context->Truncate();

	return(rv);
}



NS_IMETHODIMP
InternetSearchDataSource::GetInternetSearchURL(const char *searchEngineURI,
  const PRUnichar *searchStr, PRInt16 direction, PRUint16 pageNumber, 
  PRUint16 *whichButtons, char **resultURL)
{
	if (!resultURL)	return(NS_ERROR_NULL_POINTER);
	*resultURL = nsnull;

	// if we haven't already, load in the engines
  if (!gEngineListBuilt)
    DeferredInit();

	nsresult			rv;
	nsCOMPtr<nsIRDFResource>	engine;
	if (NS_FAILED(rv = gRDFService->GetResource(nsDependentCString(searchEngineURI), getter_AddRefs(engine))))
		return(rv);
	if (!engine)	return(NS_ERROR_UNEXPECTED);

	validateEngine(engine);
	
	// if its a engine from a search category, then get its "#Name",
	// and try to map from that back to the real engine reference
	if (isSearchCategoryEngineURI(engine))
	{
		nsCOMPtr<nsIRDFResource>	trueEngine;
		rv = resolveSearchCategoryEngineURI(engine, getter_AddRefs(trueEngine));
		if (NS_FAILED(rv) || (rv == NS_RDF_NO_VALUE))	return(rv);
		if (!trueEngine)	return(NS_RDF_NO_VALUE);

		engine = trueEngine;
	}

	nsCOMPtr<nsIRDFLiteral>	dataLit;
	rv = FindData(engine, getter_AddRefs(dataLit));
	if (NS_FAILED(rv) ||
	    (rv == NS_RDF_NO_VALUE))	return(rv);
	if (!dataLit)	return(NS_ERROR_UNEXPECTED);

	const PRUnichar	*dataUni = nsnull;
	dataLit->GetValueConst(&dataUni);
	if (!dataUni)	return(NS_RDF_NO_VALUE);

	nsAutoString	 text(searchStr), encodingStr, queryEncodingStr;

	// first look for "search/queryCharset"... if that isn't specified,
	// then fall back to looking for "search/queryEncoding" (a decimal)
	GetData(dataUni, "search", 0, "queryCharset", queryEncodingStr);
       if (queryEncodingStr.IsEmpty())
	{
		GetData(dataUni, "search", 0, "queryEncoding", encodingStr);		// decimal string values
		MapEncoding(encodingStr, queryEncodingStr);
	}

	if (!queryEncodingStr.IsEmpty())
	{
 		// remember query charset string
 		mQueryEncodingStr = queryEncodingStr;
		// convert from escaped-UTF_8, to unicode, and then to
		// the charset indicated by the dataset in question

		char	*utf8data = ToNewUTF8String(text);
		if (utf8data)
		{
			nsCOMPtr<nsITextToSubURI> textToSubURI = 
			         do_GetService(kTextToSubURICID, &rv);
			if (NS_SUCCEEDED(rv) && (textToSubURI))
			{
				PRUnichar	*uni = nsnull;
				if (NS_SUCCEEDED(rv = textToSubURI->UnEscapeAndConvert("UTF-8", utf8data, &uni)) && (uni))
				{
					char		*charsetData = nsnull;
					nsCAutoString	queryencodingstrC;
					queryencodingstrC.AssignWithConversion(queryEncodingStr);
					if (NS_SUCCEEDED(rv = textToSubURI->ConvertAndEscape(queryencodingstrC.get(), uni, &charsetData)) && (charsetData))
					{
						text.AssignWithConversion(charsetData);
						Recycle(charsetData);
					}
					Recycle(uni);
				}
			}
			Recycle(utf8data);
		}
	}
	
	nsAutoString	action, input, method, userVar, name;
	if (NS_FAILED(rv = GetData(dataUni, "search", 0, "action", action)))
	    return(rv);

    // Search only supports the http protocol
    if (!StringBeginsWith(action, NS_LITERAL_STRING("http:")) &&
        !StringBeginsWith(action, NS_LITERAL_STRING("https:")))
        return NS_ERROR_UNEXPECTED;

	if (NS_FAILED(rv = GetData(dataUni, "search", 0, "method", method)))
	    return(rv);
	if (NS_FAILED(rv = GetData(dataUni, "search", 0, "name", name)))
	    return(rv);
	if (NS_FAILED(rv = GetInputs(dataUni, name, userVar, text, input, direction, pageNumber, whichButtons)))
	    return(rv);
	if (input.IsEmpty())				return(NS_ERROR_UNEXPECTED);

	// we can only handle HTTP GET
	if (!method.LowerCaseEqualsLiteral("get"))	return(NS_ERROR_UNEXPECTED);
	// HTTP Get method support
	action += input;

	// return a copy of the resulting search URL
	*resultURL = ToNewCString(action);
	return(NS_OK);
}



NS_IMETHODIMP
InternetSearchDataSource::RememberLastSearchText(const PRUnichar *escapedSearchStr)
{
	nsresult		rv;

	nsCOMPtr<nsIRDFNode>	textNode;
	if (NS_SUCCEEDED(rv = mInner->GetTarget(kNC_LastSearchRoot, kNC_LastText, PR_TRUE,
		getter_AddRefs(textNode))))
	{
		if (escapedSearchStr != nsnull)
		{
			nsresult		temprv;
			nsCOMPtr<nsIRDFLiteral>	textLiteral;
			if (NS_SUCCEEDED(temprv = gRDFService->GetLiteral(escapedSearchStr, getter_AddRefs(textLiteral))))
			{
				if (rv != NS_RDF_NO_VALUE)
				{
					mInner->Change(kNC_LastSearchRoot, kNC_LastText, textNode, textLiteral);
				}
				else
				{
					mInner->Assert(kNC_LastSearchRoot, kNC_LastText, textLiteral, PR_TRUE);
				}
			}
		}
		else if (rv != NS_RDF_NO_VALUE)
		{
			rv = mInner->Unassert(kNC_LastSearchRoot, kNC_LastText, textNode);
		}
	}
	return(rv);
}



NS_IMETHODIMP
InternetSearchDataSource::FindInternetSearchResults(const char *url, PRBool *searchInProgress)
{
	*searchInProgress = PR_FALSE;

	if (!mInner)	return(NS_OK);

	// if the url doesn't look like a HTTP GET query, just return,
	// otherwise strip off the query data
	nsAutoString		shortURL;
	shortURL.AssignWithConversion(url);
	PRInt32			optionsOffset;
	if ((optionsOffset = shortURL.FindChar(PRUnichar('?'))) < 0)	return(NS_OK);
	shortURL.Truncate(optionsOffset);

	// if we haven't already, load in the engines
  if (!gEngineListBuilt)
    DeferredInit();

	// look in available engines to see if any of them appear
	// to match this url (minus the GET options)
	PRBool				foundEngine = PR_FALSE;
	nsresult			rv;
	nsCOMPtr<nsIRDFResource>	engine;
	nsCOMPtr<nsISimpleEnumerator>	arcs;
	nsAutoString			engineURI;
	nsCOMPtr<nsIRDFLiteral>		dataLit;
	const PRUnichar			*dataUni = nsnull;

	if (NS_SUCCEEDED(rv = mInner->GetTargets(kNC_SearchEngineRoot, kNC_Child,
		PR_TRUE, getter_AddRefs(arcs))))
	{
		PRBool			hasMore = PR_TRUE;
    while (hasMore)
		{
      if (NS_FAILED(arcs->HasMoreElements(&hasMore)) || !hasMore)
				break;
			nsCOMPtr<nsISupports>	arc;
			if (NS_FAILED(arcs->GetNext(getter_AddRefs(arc))))
				break;

			engine = do_QueryInterface(arc);
			if (!engine)	continue;

			const	char	*uri = nsnull;
			engine->GetValueConst(&uri);
			if (uri)
			{
				engineURI.AssignWithConversion(uri);
			}

			if (NS_FAILED(rv = FindData(engine, getter_AddRefs(dataLit))) ||
				(rv == NS_RDF_NO_VALUE))	continue;
			if (!dataLit)	continue;

			dataLit->GetValueConst(&dataUni);
			if (!dataUni)	continue;

			nsAutoString		action;
			if (NS_FAILED(rv = GetData(dataUni, "search", 0, "action", action)))	continue;
			if (shortURL.Equals(action, nsCaseInsensitiveStringComparator()))
			{
				foundEngine = PR_TRUE;
				break;
			}

			// extension for engines which can have multiple "actions"
			if (NS_FAILED(rv = GetData(dataUni, "browser", 0, "alsomatch", action)))	continue;
			if (nsString_Find(shortURL, action, PR_TRUE) >= 0)
			{
				foundEngine = PR_TRUE;
				break;
			}
		}
	}
  if (foundEngine)
	{
		nsAutoString	searchURL;
		searchURL.AssignWithConversion(url);

		// look for query option which is the string the user is searching for
 		nsAutoString	userVar, inputUnused, engineNameStr;
  	GetData(dataUni, "search", 0, "name", engineNameStr);

    if (NS_FAILED(rv = GetInputs(dataUni, engineNameStr, userVar, EmptyString(), inputUnused, 0, 0, 0)))  return(rv);
		if (userVar.IsEmpty())	return(NS_RDF_NO_VALUE);

		nsAutoString	queryStr;
		queryStr = NS_LITERAL_STRING("?") +
		           userVar +
		           NS_LITERAL_STRING("=");

		PRInt32		queryOffset;
		if ((queryOffset = nsString_Find(queryStr, searchURL, PR_TRUE )) < 0)
		{
			queryStr = NS_LITERAL_STRING("&") +
			           userVar +
			           NS_LITERAL_STRING("=");
			queryOffset = nsString_Find(queryStr, searchURL, PR_TRUE);
		}

		nsAutoString	searchText;
		if (queryOffset >= 0)
		{
			PRInt32		andOffset;
			searchURL.Right(searchText, searchURL.Length() - queryOffset - queryStr.Length());

			if ((andOffset = searchText.FindChar(PRUnichar('&'))) >= 0)
			{
				searchText.Truncate(andOffset);
			}
		}
		if (!searchText.IsEmpty())
		{
 			// apply charset conversion to the search text
 			if (!mQueryEncodingStr.IsEmpty())
 			{
 				nsCOMPtr<nsITextToSubURI> textToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
 				if (NS_SUCCEEDED(rv))
 				{
 					nsCAutoString	escapedSearchText;
 					escapedSearchText.AssignWithConversion(searchText);

					// encoding +'s so as to preserve distinction between + and %2B
					escapedSearchText.ReplaceSubstring("%25", "%2B25");
					escapedSearchText.ReplaceSubstring("+", "%25");

 					nsCAutoString	aCharset;
 					aCharset.AssignWithConversion(mQueryEncodingStr);
 					PRUnichar	*uni = nsnull;
 					if (NS_SUCCEEDED(rv = textToSubURI->UnEscapeAndConvert(aCharset.get(), escapedSearchText.get(), &uni)) && (uni))
 					{
 						char	*convertedSearchText = nsnull;
 						if (NS_SUCCEEDED(rv = textToSubURI->ConvertAndEscape("UTF-8", uni, &convertedSearchText)))
 						{

							// decoding +'s thereby preserving distinction between + and %2B
 							nsCAutoString unescapedSearchText(convertedSearchText);
							unescapedSearchText.ReplaceSubstring("%25", "+");
							unescapedSearchText.ReplaceSubstring("%2B25", "%25");

 							searchText.AssignWithConversion(unescapedSearchText.get());

 							Recycle(convertedSearchText);
 						}
						Recycle(uni);
 					}
 				}
 			}
			// remember the text of the last search
			RememberLastSearchText(searchText.get());

			// construct the search query uri
			engineURI.Assign(NS_LITERAL_STRING("internetsearch:engine=") + engineURI +
			                 NS_LITERAL_STRING("&text=") + searchText);

#ifdef	DEBUG_SEARCH_OUTPUT
			char	*engineMatch = ToNewCString(searchText);
			if (engineMatch)
			{
				printf("FindInternetSearchResults: search for: '%s'\n\n",
					engineMatch);
				Recycle(engineMatch);
				engineMatch = nsnull;
			}
#endif
		}
		else
		{
			// not able to determine engine URI, so just use the search URL
			engineURI = searchURL;

			// clear the text of the last search
			RememberLastSearchText(nsnull);
		}

		// update "#Ref" on LastSearchRoot
		nsCOMPtr<nsIRDFNode>	oldNode;
		if (NS_SUCCEEDED(rv = mInner->GetTarget(kNC_LastSearchRoot, kNC_Ref, PR_TRUE,
			getter_AddRefs(oldNode))))
		{
			if (!engineURI.IsEmpty())
			{
				const PRUnichar	*uriUni = engineURI.get();
				nsCOMPtr<nsIRDFLiteral>	uriLiteral;
				nsresult		temprv;
				if ((uriUni) && (NS_SUCCEEDED(temprv = gRDFService->GetLiteral(uriUni,
					getter_AddRefs(uriLiteral)))))
				{
					if (rv != NS_RDF_NO_VALUE)
					{
						rv = mInner->Change(kNC_LastSearchRoot, kNC_Ref, oldNode, uriLiteral);
					}
					else
					{
						rv = mInner->Assert(kNC_LastSearchRoot, kNC_Ref, uriLiteral, PR_TRUE);
					}
				}
			}
			else
			{
				rv = mInner->Unassert(kNC_LastSearchRoot, kNC_Ref, oldNode);
			}
		}

		// forget about any previous search results
		ClearResults(PR_FALSE);

		// do the search
		DoSearch(nsnull, engine, searchURL, EmptyString());

		*searchInProgress = PR_TRUE;
	}

	return(NS_OK);
}



NS_IMETHODIMP
InternetSearchDataSource::ClearResults(PRBool flushLastSearchRef)
{
	if (!mInner)	return(NS_ERROR_UNEXPECTED);

	// forget any nodes under the last search root
	nsresult			rv;
	nsCOMPtr<nsISimpleEnumerator>	arcs;
	if (NS_SUCCEEDED(rv = mInner->GetTargets(kNC_LastSearchRoot, kNC_Child, PR_TRUE, getter_AddRefs(arcs))))
	{
		PRBool			hasMore = PR_TRUE;
    while (hasMore)
		{
      if (NS_FAILED(arcs->HasMoreElements(&hasMore)) || !hasMore)
				break;
			nsCOMPtr<nsISupports>	arc;
			if (NS_FAILED(arcs->GetNext(getter_AddRefs(arc))))
				break;
			nsCOMPtr<nsIRDFResource> child (do_QueryInterface(arc));
			if (child)
			{
				mInner->Unassert(kNC_LastSearchRoot, kNC_Child, child);
			}

			// *after* (so that we won't thrash the XUL template builder) unasserting
			// child node, determine if there are any other references to the child
			// node in the graph

			PRBool hasInArcs = PR_FALSE;
			nsCOMPtr<nsISimpleEnumerator>	inArcs;
			if (NS_FAILED(mInner->ArcLabelsIn(child, getter_AddRefs(inArcs))) ||
				(!inArcs))
				continue;
      if (NS_FAILED(inArcs->HasMoreElements(&hasInArcs)) || hasInArcs)
				continue;

			// no other references, so also unassert any outgoing arcs

			nsCOMPtr<nsISimpleEnumerator>	outArcs;
			if (NS_FAILED(mInner->ArcLabelsOut(child, getter_AddRefs(outArcs))) ||
				(!outArcs))
				continue;
			PRBool	hasMoreOutArcs = PR_TRUE;
      while (hasMoreOutArcs)
			{
        if (NS_FAILED(outArcs->HasMoreElements(&hasMoreOutArcs)) ||
            !hasMoreOutArcs)
					break;
				nsCOMPtr<nsISupports>	outArc;
				if (NS_FAILED(outArcs->GetNext(getter_AddRefs(outArc))))
					break;
				nsCOMPtr<nsIRDFResource> property (do_QueryInterface(outArc));
				if (!property)
					continue;
				nsCOMPtr<nsIRDFNode> target;
				if (NS_FAILED(mInner->GetTarget(child, property, PR_TRUE,
					getter_AddRefs(target))) || (!target))
					continue;
				mInner->Unassert(child, property, target);
			}
		}
	}

  if (flushLastSearchRef)
	{
		// forget the last search query
		nsCOMPtr<nsIRDFNode>	lastTarget;
		if (NS_SUCCEEDED(rv = mInner->GetTarget(kNC_LastSearchRoot, kNC_Ref,
			PR_TRUE, getter_AddRefs(lastTarget))) && (rv != NS_RDF_NO_VALUE))
		{
			nsCOMPtr<nsIRDFLiteral>	lastLiteral (do_QueryInterface(lastTarget));
			if (lastLiteral)
			{
				rv = mInner->Unassert(kNC_LastSearchRoot, kNC_Ref, lastLiteral);
			}
		}
	}

	return(NS_OK);
}



NS_IMETHODIMP
InternetSearchDataSource::ClearResultSearchSites(void)
{
	// forget about any previous search sites

	if (mInner)
	{
		nsresult			rv;
		nsCOMPtr<nsISimpleEnumerator>	arcs;
		if (NS_SUCCEEDED(rv = mInner->GetTargets(kNC_SearchResultsSitesRoot, kNC_Child, PR_TRUE, getter_AddRefs(arcs))))
		{
			PRBool			hasMore = PR_TRUE;
      while (hasMore)
			{
        if (NS_FAILED(arcs->HasMoreElements(&hasMore)) || !hasMore)
					break;
				nsCOMPtr<nsISupports>	arc;
				if (NS_FAILED(arcs->GetNext(getter_AddRefs(arc))))
					break;
				nsCOMPtr<nsIRDFResource> child (do_QueryInterface(arc));
				if (child)
				{
					mInner->Unassert(kNC_SearchResultsSitesRoot, kNC_Child, child);
				}
			}
		}
	}
	return(NS_OK);
}



NS_IMETHODIMP
InternetSearchDataSource::GetCategoryDataSource(nsIRDFDataSource **ds)
{
	nsresult	rv;

	if (!categoryDataSource)
	{
		if (NS_FAILED(rv = GetCategoryList()))
		{
			*ds = nsnull;
			return(rv);
		}
	}
	if (categoryDataSource)
	{
		*ds = categoryDataSource.get();
		NS_IF_ADDREF(*ds);
		return(NS_OK);
	}
	*ds = nsnull;
	return(NS_ERROR_FAILURE);
}



NS_IMETHODIMP
InternetSearchDataSource::Stop()
{
	nsresult		rv;

	// cancel any outstanding connections
	if (mLoadGroup)
	{
		nsCOMPtr<nsISimpleEnumerator>	requests;
		if (NS_SUCCEEDED(rv = mLoadGroup->GetRequests(getter_AddRefs(requests))))
		{
			PRBool			more;
      while (NS_SUCCEEDED(rv = requests->HasMoreElements(&more)) && more)
			{
				nsCOMPtr<nsISupports>	isupports;
				if (NS_FAILED(rv = requests->GetNext(getter_AddRefs(isupports))))
					break;
				nsCOMPtr<nsIRequest> request (do_QueryInterface(isupports));
				if (!request)	continue;
				request->Cancel(NS_BINDING_ABORTED);
			}
		}
		mLoadGroup->Cancel(NS_BINDING_ABORTED);
	}

	// remove any loading icons
	nsCOMPtr<nsISimpleEnumerator>	arcs;
	if (NS_SUCCEEDED(rv = mInner->GetSources(kNC_loading, kTrueLiteral, PR_TRUE,
		getter_AddRefs(arcs))))
	{
		PRBool			hasMore = PR_TRUE;
    while (hasMore)
		{
      if (NS_FAILED(arcs->HasMoreElements(&hasMore)) || !hasMore)
				break;
			nsCOMPtr<nsISupports>	arc;
			if (NS_FAILED(arcs->GetNext(getter_AddRefs(arc))))
				break;
			nsCOMPtr<nsIRDFResource> src (do_QueryInterface(arc));
			if (src)
			{
				mInner->Unassert(src, kNC_loading, kTrueLiteral);
			}
		}
	}

	return(NS_OK);
}



nsresult
InternetSearchDataSource::BeginSearchRequest(nsIRDFResource *source, PRBool doNetworkRequest)
{
        nsresult		rv = NS_OK;
	const char		*sourceURI = nsnull;

	if (NS_FAILED(rv = source->GetValueConst(&sourceURI)))
		return(rv);
	nsAutoString		uri;
	uri.AssignWithConversion(sourceURI);

	if (uri.Find("internetsearch:") != 0)
		return(NS_ERROR_FAILURE);

	// forget about any previous search results
	ClearResults(PR_TRUE);

	// forget about any previous search sites
	ClearResultSearchSites();

	// remember the last search query
	const PRUnichar	*uriUni = uri.get();
	nsCOMPtr<nsIRDFLiteral>	uriLiteral;
	if (NS_SUCCEEDED(rv = gRDFService->GetLiteral(uriUni, getter_AddRefs(uriLiteral))))
	{
		rv = mInner->Assert(kNC_LastSearchRoot, kNC_Ref, uriLiteral, PR_TRUE);
	}

	uri.Cut(0, strlen("internetsearch:"));

	nsVoidArray	*engineArray = new nsVoidArray;
	if (!engineArray)
		return(NS_ERROR_FAILURE);

	nsAutoString	text;

	// parse up attributes

	while(!uri.IsEmpty())
	{
		nsAutoString	item;

		PRInt32 andOffset = uri.Find("&");
		if (andOffset >= 0)
		{
			uri.Left(item, andOffset);
			uri.Cut(0, andOffset + 1);
		}
		else
		{
			item = uri;
			uri.Truncate();
		}

		PRInt32 equalOffset = item.Find("=");
		if (equalOffset < 0)	break;
		
		nsAutoString	attrib, value;
		item.Left(attrib, equalOffset);
		value = item;
		value.Cut(0, equalOffset + 1);
		
		if (!attrib.IsEmpty() && !value.IsEmpty())
		{
			if (attrib.LowerCaseEqualsLiteral("engine"))
			{
				if ((value.Find(kEngineProtocol) == 0) ||
					(value.Find(kURINC_SearchCategoryEnginePrefix) == 0))
				{
					char	*val = ToNewCString(value);
					if (val)
					{
						engineArray->AppendElement(val);
					}
				}
			}
			else if (attrib.LowerCaseEqualsLiteral("text"))
			{
				text = value;
			}
		}
	}

	mInner->Assert(source, kNC_loading, kTrueLiteral, PR_TRUE);

	PRBool	requestInitiated = PR_FALSE;

	// loop over specified search engines
	while (engineArray->Count() > 0)
	{
		char *baseFilename = (char *)(engineArray->ElementAt(0));
		engineArray->RemoveElementAt(0);
		if (!baseFilename)	continue;

#ifdef	DEBUG_SEARCH_OUTPUT
		printf("Search engine to query: '%s'\n", baseFilename);
#endif

		nsCOMPtr<nsIRDFResource>	engine;
		gRDFService->GetResource(nsDependentCString(baseFilename), getter_AddRefs(engine));
		nsCRT::free(baseFilename);
		baseFilename = nsnull;
		if (!engine)	continue;

		// if its a engine from a search category, then get its "#Name",
		// and map from that back to the real engine reference; if an
		// error occurs, finish processing the rest of the engines,
		// don't just break/return out
		if (isSearchCategoryEngineURI(engine))
		{
			nsCOMPtr<nsIRDFResource>	trueEngine;
			rv = resolveSearchCategoryEngineURI(engine, getter_AddRefs(trueEngine));
			if (NS_FAILED(rv) || (rv == NS_RDF_NO_VALUE))	return(rv);
			if (!trueEngine)	continue;

			engine = trueEngine;
		}

		// mark this as a search site
		if (mInner)
		{
			mInner->Assert(kNC_SearchResultsSitesRoot, kNC_Child, engine, PR_TRUE);
		}

    if (doNetworkRequest)
		{
			DoSearch(source, engine, EmptyString(), text);
			requestInitiated = PR_TRUE;
		}
	}
	
	delete engineArray;
	engineArray = nsnull;

  if (!requestInitiated)
	{
		Stop();
	}

	return(rv);
}



nsresult
InternetSearchDataSource::FindData(nsIRDFResource *engine, nsIRDFLiteral **dataLit)
{
	if (!engine)	return(NS_ERROR_NULL_POINTER);
	if (!dataLit)	return(NS_ERROR_NULL_POINTER);

	*dataLit = nsnull;

	if (!mInner)	return(NS_RDF_NO_VALUE);

	nsresult		rv;

	nsCOMPtr<nsIRDFNode>	dataTarget = nsnull;
	if (NS_SUCCEEDED((rv = mInner->GetTarget(engine, kNC_Data, PR_TRUE,
		getter_AddRefs(dataTarget)))) && (dataTarget))
	{
		nsCOMPtr<nsIRDFLiteral>	aLiteral (do_QueryInterface(dataTarget));
		if (!aLiteral)
			return(NS_ERROR_UNEXPECTED);
		*dataLit = aLiteral;
		NS_IF_ADDREF(*dataLit);
		return(NS_OK);
	}

        nsCOMPtr<nsILocalFile> engineFile;
        rv = EngineFileFromResource(engine, getter_AddRefs(engineFile));
        if (NS_FAILED(rv)) return rv;

        nsString	data;
        rv = ReadFileContents(engineFile, data);

	if (NS_FAILED(rv))
	{
		return(rv);
	}

	// save file contents
	if (data.IsEmpty())	return(NS_ERROR_UNEXPECTED);

	rv = updateDataHintsInGraph(engine, data.get());

	nsCOMPtr<nsIRDFLiteral>	aLiteral;
	if (NS_SUCCEEDED(rv = gRDFService->GetLiteral(data.get(), getter_AddRefs(aLiteral))))
	{
		*dataLit = aLiteral;
		NS_IF_ADDREF(*dataLit);
	}
	
	return(rv);
}

nsresult
InternetSearchDataSource::EngineFileFromResource(nsIRDFResource *aResource,
                                                 nsILocalFile **aResult)
{
  nsresult rv = NS_OK;

  // get resource uri
  const char *engineURI = nsnull;
  rv = aResource->GetValueConst(&engineURI);
  NS_ENSURE_SUCCESS(rv, rv);

  // remove protocol from uri and get escaped file path
  nsCAutoString nativePath;
  nativePath.Assign(engineURI);

  NS_ENSURE_TRUE(StringBeginsWith(nativePath,
                                  NS_LITERAL_CSTRING(kEngineProtocol)),
                 NS_ERROR_FAILURE);
  nativePath.Cut(0, sizeof(kEngineProtocol) - 1);

  // unescape it
  NS_UnescapeURL(nativePath);

#ifdef DEBUG_SEARCH_OUTPUT
  printf("InternetSearchDataSource::EngineFileFromResource\n"
         "File Path: %s\n",
         nativePath.get());
#endif

  rv = NS_NewNativeLocalFile(nativePath, PR_TRUE, aResult);

  return rv;
}

nsresult
InternetSearchDataSource::DecodeData(const char *aCharset, const PRUnichar *aInString, PRUnichar **aOutString)
{
	nsresult rv;
    
	nsCOMPtr <nsICharsetConverterManager> charsetConv = 
          do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv);
	NS_ENSURE_SUCCESS(rv, rv);

	nsCOMPtr<nsIUnicodeDecoder>	unicodeDecoder;
	rv = charsetConv->GetUnicodeDecoder(aCharset, getter_AddRefs(unicodeDecoder));

	// Use the sherlock default charset in case of failure
	if (NS_FAILED(rv))
		rv = charsetConv->GetUnicodeDecoderRaw("x-mac-roman", getter_AddRefs(unicodeDecoder));
	NS_ENSURE_SUCCESS(rv, rv);

	// This fixes the corruption occured in InternetSearchDataSource::ReadFileContents()
	// (eg. aValue contains "0x0082 0x00a1" for "0x82 0xa1" shift_jis double-byte char)
	NS_LossyConvertUCS2toASCII value(aInString);

	PRInt32 srcLength = value.Length();
	PRInt32 outUnicodeLen;
	rv = unicodeDecoder->GetMaxLength(value.get(), srcLength, &outUnicodeLen);
	NS_ENSURE_SUCCESS(rv, rv);

	*aOutString = NS_REINTERPRET_CAST(PRUnichar*, nsMemory::Alloc((outUnicodeLen + 1) * sizeof(PRUnichar)));
	NS_ENSURE_TRUE(*aOutString, NS_ERROR_OUT_OF_MEMORY);

	rv = unicodeDecoder->Convert(value.get(), &srcLength, *aOutString, &outUnicodeLen);
	NS_ENSURE_SUCCESS(rv, rv);
	(*aOutString)[outUnicodeLen] = (PRUnichar)'\0';

	return rv;
}

nsresult
InternetSearchDataSource::updateDataHintsInGraph(nsIRDFResource *engine, const PRUnichar *dataUni)
{
	nsresult	rv = NS_OK;

	// save/update search engine data
	nsCOMPtr<nsIRDFLiteral>	dataLiteral;
	if (NS_SUCCEEDED(rv = gRDFService->GetLiteral(dataUni, getter_AddRefs(dataLiteral))))
	{
		updateAtom(mInner, engine, kNC_Data, dataLiteral, nsnull);
	}

	// save/update name of search engine (as specified in file)
	nsAutoString scriptCodeValue;
	const char * charsetName = MapScriptCodeToCharsetName(0);
	nsXPIDLString decodedValue;

	if (NS_SUCCEEDED(rv = GetData(dataUni, "search", 0, "sourceTextEncoding", scriptCodeValue)) && 
		!scriptCodeValue.IsEmpty())
	{
		PRInt32 err;
		PRInt32 scriptCode = scriptCodeValue.ToInteger(&err);
		if (NS_SUCCEEDED(err))
			charsetName = MapScriptCodeToCharsetName(scriptCode);
	}

	nsAutoString	nameValue;
	if (NS_SUCCEEDED(rv = GetData(dataUni, "search", 0, "name", nameValue)))
	{
		rv = DecodeData(charsetName, nameValue.get(), getter_Copies(decodedValue));
		nsCOMPtr<nsIRDFLiteral>	nameLiteral;
		if (NS_SUCCEEDED(rv) &&
				NS_SUCCEEDED(rv = gRDFService->GetLiteral(decodedValue.get(),
				                                          getter_AddRefs(nameLiteral))))
		{
			rv = updateAtom(mInner, engine, kNC_Name, nameLiteral, nsnull);
		}
	}

	// save/update description of search engine (if specified)
	nsAutoString	descValue;
	if (NS_SUCCEEDED(rv = GetData(dataUni, "search", 0, "description", descValue)))
	{
		rv = DecodeData(charsetName, descValue.get(), getter_Copies(decodedValue));
		nsCOMPtr<nsIRDFLiteral>	descLiteral;
		if (NS_SUCCEEDED(rv) &&
				NS_SUCCEEDED(rv = gRDFService->GetLiteral(decodedValue.get(),
				                                          getter_AddRefs(descLiteral))))
		{
			rv = updateAtom(mInner, engine, kNC_Description, descLiteral, nsnull);
		}
	}

	// save/update version of search engine (if specified)
	nsAutoString	versionValue;
	if (NS_SUCCEEDED(rv = GetData(dataUni, "search", 0, "version", versionValue)))
	{
		nsCOMPtr<nsIRDFLiteral>	versionLiteral;
		if (NS_SUCCEEDED(rv = gRDFService->GetLiteral(versionValue.get(),
				getter_AddRefs(versionLiteral))))
		{
			rv = updateAtom(mInner, engine, kNC_Version, versionLiteral, nsnull);
		}
	}

	nsAutoString	buttonValue;
	if (NS_SUCCEEDED(rv = GetData(dataUni, "search", 0, "actionButton", buttonValue)))
	{
		nsCOMPtr<nsIRDFLiteral>	buttonLiteral;
		if (NS_SUCCEEDED(rv = gRDFService->GetLiteral(buttonValue.get(),
				getter_AddRefs(buttonLiteral))))
		{
			rv = updateAtom(mInner, engine, kNC_actionButton, buttonLiteral, nsnull);
		}
	}

	nsAutoString	barValue;
	if (NS_SUCCEEDED(rv = GetData(dataUni, "search", 0, "actionBar", barValue)))
	{
		nsCOMPtr<nsIRDFLiteral>	barLiteral;
		if (NS_SUCCEEDED(rv = gRDFService->GetLiteral(barValue.get(),
				getter_AddRefs(barLiteral))))
		{
			rv = updateAtom(mInner, engine, kNC_actionBar, barLiteral, nsnull);
		}
	}

	nsAutoString	searchFormValue;
	if (NS_SUCCEEDED(rv = GetData(dataUni, "search", 0, "searchForm", searchFormValue)))
	{
		nsCOMPtr<nsIRDFLiteral>	searchFormLiteral;
		if (NS_SUCCEEDED(rv = gRDFService->GetLiteral(searchFormValue.get(),
				getter_AddRefs(searchFormLiteral))))
		{
			rv = updateAtom(mInner, engine, kNC_searchForm, searchFormLiteral, nsnull);
		}
	}


	PRBool	updatePrivateFiles = PR_FALSE;

  rv = mInner->HasAssertion(engine, kNC_SearchType, kNC_Engine, PR_TRUE,
                            &updatePrivateFiles);
  if (NS_SUCCEEDED(rv) && updatePrivateFiles)
	{
		// rjc says: so here is our strategy. The spec says that the "search"|"update"
		// attribute MUST refer to a binhex-ed file (i.e. ends with ".src.hqx"). We don't
		// support binhex (due to being cross-platform minded folks) and, even if we did,
		// its not enough info to figure out what the appropriate icon should be.
		// The answer: we're add additional attributes in our private <BROWSER> section
		// to re-define these values. If they aren't there, we'll fallback to the <SEARCH>
		// section values and try and make logical choices:  for example, if we have an
		// "update" URL which ends with ".src.hqx", we'll strip off the ".hqx" part and
		// check to see if it exists... which at least makes it possible for a web site
		// to put up both a binhexed version and a straight text version of the search file.

		// get update URL and # of days to check for updates
		// Note: only check for updates on our private search files
		nsAutoString	updateStr, updateIconStr, updateCheckDaysStr;

		GetData(dataUni, "browser", 0, "update", updateStr);
		if (updateStr.IsEmpty())
		{
			// fallback to trying "search"|"updateCheckDays"
			GetData(dataUni, "search", 0, "update", updateStr);

			// if we have a ".hqx" extension, strip it off
			nsAutoString	extension;
			updateStr.Right(extension, 4);
			if (extension.LowerCaseEqualsLiteral(".hqx"))
			{
				updateStr.Truncate(updateStr.Length() - 4);
			}

			// now, either way, ensure that we have a ".src" file
			updateStr.Right(extension, 4);
      if (!extension.LowerCaseEqualsLiteral(".src"))
			{
				// and if we don't, toss it
				updateStr.Truncate();
			}
		}
		else
		{
			// note: its OK if the "updateIcon" isn't specified
			GetData(dataUni, "browser", 0, "updateIcon", updateIconStr);
		}
		if (!updateStr.IsEmpty())
		{
			GetData(dataUni, "browser", 0, "updateCheckDays", updateCheckDaysStr);
			if (updateCheckDaysStr.IsEmpty())
			{
				// fallback to trying "search"|"updateCheckDays"
				GetData(dataUni, "search", 0, "updateCheckDays", updateCheckDaysStr);
			}
		}

		if (!updateStr.IsEmpty() && !updateCheckDaysStr.IsEmpty())
		{
			nsCOMPtr<nsIRDFLiteral>	updateLiteral;
			if (NS_SUCCEEDED(rv = gRDFService->GetLiteral(updateStr.get(),
					getter_AddRefs(updateLiteral))))
			{
				rv = updateAtom(mInner, engine, kNC_Update, updateLiteral, nsnull);
			}

			PRInt32	err;
			PRInt32	updateDays = updateCheckDaysStr.ToInteger(&err);
			if ((err) || (updateDays < 1))
			{
				// default to something sane
				updateDays = 3;
			}

			nsCOMPtr<nsIRDFInt>	updateCheckDaysLiteral;
			if (NS_SUCCEEDED(rv = gRDFService->GetIntLiteral(updateDays,
					getter_AddRefs(updateCheckDaysLiteral))))
			{
				rv = updateAtom(mInner, engine, kNC_UpdateCheckDays, updateCheckDaysLiteral, nsnull);
			}

			if (!updateIconStr.IsEmpty())
			{
				nsCOMPtr<nsIRDFLiteral>	updateIconLiteral;
				if (NS_SUCCEEDED(rv = gRDFService->GetLiteral(updateIconStr.get(),
						getter_AddRefs(updateIconLiteral))))
				{
					rv = updateAtom(mInner, engine, kNC_UpdateIcon, updateIconLiteral, nsnull);
				}
			}
		}
	}

	return(rv);
}



nsresult
InternetSearchDataSource::updateAtom(nsIRDFDataSource *db, nsIRDFResource *src,
			nsIRDFResource *prop, nsIRDFNode *newValue, PRBool *dirtyFlag)
{
	nsresult		rv;
	nsCOMPtr<nsIRDFNode>	oldValue;

	if (dirtyFlag != nsnull)
	{
		*dirtyFlag = PR_FALSE;
	}

	if (NS_SUCCEEDED(rv = db->GetTarget(src, prop, PR_TRUE, getter_AddRefs(oldValue))) &&
		(rv != NS_RDF_NO_VALUE))
	{
		rv = db->Change(src, prop, oldValue, newValue);

		if ((oldValue.get() != newValue) && (dirtyFlag != nsnull))
		{
			*dirtyFlag = PR_TRUE;
		}
	}
	else
	{
		rv = db->Assert(src, prop, newValue, PR_TRUE);
		if (dirtyFlag != nsnull)
		{
		    *dirtyFlag = PR_TRUE;
		}
	}
	return(rv);
}



struct	encodings
{
	const char	*numericEncoding;
	const char	*stringEncoding;
};



nsresult
InternetSearchDataSource::MapEncoding(const nsString &numericEncoding, 
                                      nsString &stringEncoding)
{
	// XXX we need to have a full table of numeric --> string conversions

	struct	encodings	encodingList[] =
	{
		{	"0", "x-mac-roman"	},
		{	"6", "x-mac-greek"	},
		{	"35", "x-mac-turkish"	},
		{	"513", "ISO-8859-1"	},
		{	"514", "ISO-8859-2"	},
		{	"517", "ISO-8859-5"	},
		{	"518", "ISO-8859-6"	},
		{	"519", "ISO-8859-7"	},
		{	"520", "ISO-8859-8"	},
		{	"521", "ISO-8859-9"	},
		{	"1049", "IBM864"	},
		{	"1280", "windows-1252"	},
		{	"1281", "windows-1250"	},
		{	"1282", "windows-1251"	},
		{	"1283", "windows-1253"	},
		{	"1284", "windows-1254"	},
		{	"1285", "windows-1255"	},
		{	"1286", "windows-1256"	},
		{	"1536", "us-ascii"	},
		{	"1584", "GB2312"	},
		{	"1585", "x-gbk"		},
		{	"1600", "EUC-KR"	},
		{	"2080", "ISO-2022-JP"	},
		{	"2096", "ISO-2022-CN"	},
		{	"2112", "ISO-2022-KR"	},
		{	"2336", "EUC-JP"	},
		{	"2352", "GB2312"	},
		{	"2353", "x-euc-tw"	},
		{	"2368", "EUC-KR"	},
		{	"2561", "Shift_JIS"	},
		{	"2562", "KOI8-R"	},
		{	"2563", "Big5"		},
		{	"2565", "HZ-GB-2312"	},

		{	nsnull, nsnull		}
	};

  if (!numericEncoding.IsEmpty())	{
    for (PRUint32 i = 0; encodingList[i].numericEncoding != nsnull; i++)
    {
      if (numericEncoding.EqualsASCII(encodingList[i].numericEncoding)) 
      {
        stringEncoding.AssignASCII(encodingList[i].stringEncoding);
        return NS_OK;
      }
    }
  }

  // Still no encoding, fall back to default charset if possible
  nsXPIDLString defCharset;
  nsCOMPtr<nsIPref> prefs(do_GetService(NS_PREF_CONTRACTID));
  if (prefs)
    prefs->GetLocalizedUnicharPref("intl.charset.default", getter_Copies(defCharset));

  if (!defCharset.IsEmpty())
    stringEncoding = defCharset;
  else
    // make "ISO-8859-1" as the default (not "UTF-8")
    stringEncoding.AssignLiteral("ISO-8859-1");

  return(NS_OK);
}



const char * const
InternetSearchDataSource::MapScriptCodeToCharsetName(PRUint32 aScriptCode)
{
	// Script codes listed here are taken from Inside Macintosh Text.
	// Used TECGetTextEncodingInternetName to map script code to charset name.
	// Some are edited to match with standard name (e.g. "x-mac-japanese" -> "Shift_JIS").
	// In case no converter can be found for a charset name, 
	// the default sherlock charset "x-mac-roman" should be used.
	// 
        static const char* const scriptList[] =
	{
		"x-mac-roman",           // 0
		"Shift_JIS",             // 1
		"Big5",                  // 2
		"EUC-KR",                // 3
		"X-MAC-ARABIC",          // 4
		"X-MAC-HEBREW",          // 5
		"X-MAC-GREEK",           // 6
		"X-MAC-CYRILLIC",        // 7
		"X-MAC-DEVANAGARI" ,     // 9
		"X-MAC-GURMUKHI",        // 10
		"X-MAC-GUJARATI",        // 11
		"X-MAC-ORIYA",           // 12
		"X-MAC-BENGALI",         // 13
		"X-MAC-TAMIL",           // 14
		"X-MAC-TELUGU",          // 15
		"X-MAC-KANNADA",         // 16
		"X-MAC-MALAYALAM",       // 17
		"X-MAC-SINHALESE",       // 18
		"X-MAC-BURMESE",         // 19
		"X-MAC-KHMER",           // 20
		"X-MAC-THAI",            // 21
		"X-MAC-LAOTIAN",         // 22
		"X-MAC-GEORGIAN",        // 23
		"X-MAC-ARMENIAN",        // 24
		"GB2312",                // 25
		"X-MAC-TIBETAN",         // 26
		"X-MAC-MONGOLIAN",       // 27
		"X-MAC-ETHIOPIC",        // 28
		"X-MAC-CENTRALEURROMAN", // 29
		"X-MAC-VIETNAMESE",      // 30
		"X-MAC-EXTARABIC",       // 31
 	};

        if (aScriptCode >= NS_ARRAY_LENGTH(scriptList))
          aScriptCode = 0;

	return scriptList[aScriptCode];
}


nsresult
InternetSearchDataSource::validateEngine(nsIRDFResource *engine)
{
	nsresult	rv;

  // confirm whether the user wants to update plugins.
  nsCOMPtr<nsIPrefBranch>
    prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
  NS_ENSURE_SUCCESS(rv, rv);

  PRBool userAllowed = PR_TRUE;
  rv = prefBranch->GetBoolPref("browser.search.update", &userAllowed);
  // if the pref value is not set or wrong type, don't stop here.
  if (NS_SUCCEEDED(rv) && !userAllowed)
    return NS_OK;

#ifdef	DEBUG_SEARCH_UPDATES
	const char	*engineURI = nsnull;
	engine->GetValueConst(&engineURI);
#endif

	// get the engines "updateCheckDays" value
	nsCOMPtr<nsIRDFNode>	updateCheckDaysNode;
	rv = mInner->GetTarget(engine, kNC_UpdateCheckDays, PR_TRUE, getter_AddRefs(updateCheckDaysNode));
	if (NS_FAILED(rv) || (rv == NS_RDF_NO_VALUE))	return(rv);
	nsCOMPtr<nsIRDFInt> updateCheckDaysLiteral (do_QueryInterface(updateCheckDaysNode));
	PRInt32		updateCheckDays;
	updateCheckDaysLiteral->GetValue(&updateCheckDays);
	// convert updateCheckDays from days to seconds;

#ifndef	DEBUG_SEARCH_UPDATES
	PRInt32		updateCheckSecs = updateCheckDays * (60 * 60 * 24);
#else
	// rjc note: for debugging, use as minutes instead days
	PRInt32		updateCheckSecs = updateCheckDays * 60;
#endif

	nsCOMPtr<nsIRDFNode>	aNode;
	rv = mLocalstore->GetTarget(engine, kWEB_LastPingDate, PR_TRUE, getter_AddRefs(aNode));
	if (NS_FAILED(rv))	return(rv);

  // if aNode is a valid entry, we should check the durationSecs.
  if (rv != NS_RDF_NO_VALUE) {
    // get last validate date/time
    nsCOMPtr<nsIRDFLiteral> lastCheckLiteral(do_QueryInterface(aNode));
    if (!lastCheckLiteral)
      return NS_ERROR_UNEXPECTED;

    const PRUnichar *lastCheckUni = nsnull;
    lastCheckLiteral->GetValueConst(&lastCheckUni);
    if (!lastCheckUni)
      return NS_ERROR_UNEXPECTED;

    PRInt32 lastCheckInt = 0, err = 0;
    lastCheckInt = nsDependentString(lastCheckUni).ToInteger(&err);
    // signed int32 -> unsigned int32
    rv = (nsresult) err;
    NS_ENSURE_SUCCESS(rv, rv);

    // get the current date/time [from microseconds (PRTime) to seconds]
    PRTime now64 = PR_Now(), temp64, million;
    LL_I2L(million, PR_USEC_PER_SEC);
    LL_DIV(temp64, now64, million);
    PRInt32 now32;
    LL_L2I(now32, temp64);

    // calculate duration since last validation
    // just return if it's too early to check again
    PRInt32 durationSecs = now32 - lastCheckInt;

    if (durationSecs < updateCheckSecs) {
#ifdef	DEBUG_SEARCH_UPDATES
      printf("    Search engine '%s' is valid for %d more seconds.\n",
             engineURI, (updateCheckSecs-durationSecs));
#endif
      return NS_OK;
    }
  }

	// search engine needs to be checked again, so add it into the to-be-validated array
	PRInt32		elementIndex = mUpdateArray->IndexOf(engine);
	if (elementIndex < 0)
	{
		mUpdateArray->AppendElement(engine);

#ifdef	DEBUG_SEARCH_UPDATES
    printf("    Search engine '%s' is now queued to be validated"
           " via HTTP HEAD method.\n",
           engineURI);
#endif
	}
	else
	{
#ifdef	DEBUG_SEARCH_UPDATES
		printf("    Search engine '%s' is already in queue to be validated.\n",
			engineURI);
#endif
	}
	return(NS_OK);
}



nsresult
InternetSearchDataSource::DoSearch(nsIRDFResource *source, nsIRDFResource *engine,
				const nsString &fullURL, const nsString &text)
{
	nsresult	rv;
	nsAutoString	textTemp(text);

	if (!mInner)	return(NS_RDF_NO_VALUE);
//	Note: source can be null
//	if (!source)	return(NS_ERROR_NULL_POINTER);
	if (!engine)	return(NS_ERROR_NULL_POINTER);

	validateEngine(engine);

	nsCOMPtr<nsIUnicodeDecoder>	unicodeDecoder;
	nsAutoString			action, methodStr, input, userVar;

	nsCOMPtr<nsIRDFLiteral>		dataLit;
	if (NS_FAILED(rv = FindData(engine, getter_AddRefs(dataLit))) ||
		(rv == NS_RDF_NO_VALUE))	return(rv);

	const PRUnichar			*dataUni = nsnull;
	dataLit->GetValueConst(&dataUni);
	if (!dataUni)			return(NS_RDF_NO_VALUE);

	if (!fullURL.IsEmpty())
	{
		action.Assign(fullURL);
		methodStr.AssignLiteral("get");
	}
	else
	{
		if (NS_FAILED(rv = GetData(dataUni, "search", 0, "action", action)))	return(rv);
		if (NS_FAILED(rv = GetData(dataUni, "search", 0, "method", methodStr)))	return(rv);
	}

	nsAutoString	encodingStr, resultEncodingStr;

	// first look for "interpret/charset"... if that isn't specified,
	// then fall back to looking for "interpret/resultEncoding" (a decimal)
	GetData(dataUni, "interpret", 0, "charset", resultEncodingStr);
	if (resultEncodingStr.IsEmpty())
	{
		GetData(dataUni, "interpret", 0, "resultEncoding", encodingStr);	// decimal string values
		MapEncoding(encodingStr, resultEncodingStr);
	}
	// rjc note: ignore "interpret/resultTranslationEncoding" as well as
	// "interpret/resultTranslationFont" since we always convert results to Unicode
	if (!resultEncodingStr.IsEmpty())
	{
		nsCOMPtr <nsICharsetConverterManager> charsetConv = do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv);
		if (NS_SUCCEEDED(rv))
		{
                  NS_LossyConvertUCS2toASCII charset(resultEncodingStr);
                  rv = charsetConv->GetUnicodeDecoder(charset.get(),
                                                      getter_AddRefs(unicodeDecoder));
		}
	}

	// first look for "search/queryCharset"... if that isn't specified,
	// then fall back to looking for "search/queryEncoding" (a decimal)
	nsAutoString    queryEncodingStr;
	GetData(dataUni, "search", 0, "queryCharset", queryEncodingStr);
	if (queryEncodingStr.IsEmpty())
	{
		GetData(dataUni, "search", 0, "queryEncoding", encodingStr);		// decimal string values
		MapEncoding(encodingStr, queryEncodingStr);
	}
	if (!queryEncodingStr.IsEmpty())
	{
		// convert from escaped-UTF_8, to unicode, and then to
		// the charset indicated by the dataset in question

		char	*utf8data = ToNewUTF8String(textTemp);
		if (utf8data)
		{
			nsCOMPtr<nsITextToSubURI> textToSubURI = 
			         do_GetService(kTextToSubURICID, &rv);
			if (NS_SUCCEEDED(rv) && (textToSubURI))
			{
				PRUnichar	*uni = nsnull;
				if (NS_SUCCEEDED(rv = textToSubURI->UnEscapeAndConvert("UTF-8", utf8data, &uni)) && (uni))
				{
					char		*charsetData = nsnull;
					nsCAutoString	queryencodingstrC;
					queryencodingstrC.AssignWithConversion(queryEncodingStr);
					if (NS_SUCCEEDED(rv = textToSubURI->ConvertAndEscape(queryencodingstrC.get(), uni, &charsetData))
					        && (charsetData))
					{
						textTemp.AssignWithConversion(charsetData);
						Recycle(charsetData);
					}
					Recycle(uni);
				}
			}
			Recycle(utf8data);
		}
	}

	if (fullURL.IsEmpty() && methodStr.LowerCaseEqualsLiteral("get"))
	{
    nsAutoString engineNameStr;
  	GetData(dataUni, "search", 0, "name", engineNameStr);

    if (NS_FAILED(rv = GetInputs(dataUni, engineNameStr, userVar, textTemp, input, 0, 0, 0)))  return(rv);
		if (input.IsEmpty())				return(NS_ERROR_UNEXPECTED);

		// HTTP Get method support
		action += input;
	}

	nsCOMPtr<nsIInternetSearchContext>	context;
	if (NS_FAILED(rv = NS_NewInternetSearchContext(nsIInternetSearchContext::WEB_SEARCH_CONTEXT,
		source, engine, unicodeDecoder, nsnull, getter_AddRefs(context))))
		return(rv);
	if (!context)	return(NS_ERROR_UNEXPECTED);

	nsCOMPtr<nsIURI>	url;
	if (NS_SUCCEEDED(rv = NS_NewURI(getter_AddRefs(url), action)))
	{
		nsCOMPtr<nsIChannel>	channel;
		if (NS_SUCCEEDED(rv = NS_NewChannel(getter_AddRefs(channel), url, nsnull, mLoadGroup)))
		{

			// send a "MultiSearch" header
			nsCOMPtr<nsIHttpChannel> httpMultiChannel (do_QueryInterface(channel));
			if (httpMultiChannel)
			{
                httpMultiChannel->SetRequestHeader(NS_LITERAL_CSTRING("MultiSearch"),
                                                   NS_LITERAL_CSTRING("true"),
                                                   PR_FALSE);
			}

			// get it just from the cache if we can (do not validate)
			channel->SetLoadFlags(nsIRequest::LOAD_FROM_CACHE);

			if (methodStr.LowerCaseEqualsLiteral("post"))
			{
				nsCOMPtr<nsIHttpChannel> httpChannel (do_QueryInterface(channel));
				if (httpChannel)
				{
				    httpChannel->SetRequestMethod(NS_LITERAL_CSTRING("POST"));
				    
				    // construct post data to send
				    nsAutoString	postStr;
				    postStr.AssignASCII(POSTHEADER_PREFIX);
				    postStr.AppendInt(input.Length(), 10);
				    postStr.AppendASCII(POSTHEADER_SUFFIX);
				    postStr += input;
				    
				    nsCOMPtr<nsIInputStream>	postDataStream;
				    nsCAutoString			poststrC;
				    poststrC.AssignWithConversion(postStr);
				    if (NS_SUCCEEDED(rv = NS_NewPostDataStream(getter_AddRefs(postDataStream),
									       PR_FALSE, poststrC, 0)))
				    {
					nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel));
					NS_ASSERTION(uploadChannel, "http must support nsIUploadChannel");
					uploadChannel->SetUploadStream(postDataStream, EmptyCString(), -1);
				    }
				}
			}
			
			nsCOMPtr<nsIRequest> request;
      rv = channel->AsyncOpen(this, context);
		}
	}

	// dispose of any last HTML results page
	if (mInner)
	{
		nsCOMPtr<nsIRDFNode>	htmlNode;
		if (NS_SUCCEEDED(rv = mInner->GetTarget(engine, kNC_HTML, PR_TRUE, getter_AddRefs(htmlNode)))
			&& (rv != NS_RDF_NO_VALUE))
		{
			rv = mInner->Unassert(engine, kNC_HTML, htmlNode);
		}
	}

	// start "loading" animation for this search engine
	if (NS_SUCCEEDED(rv) && (mInner))
	{
		// remove status icon so that loading icon style can be used
		nsCOMPtr<nsIRDFNode>		engineIconNode = nsnull;
		mInner->GetTarget(engine, kNC_StatusIcon, PR_TRUE, getter_AddRefs(engineIconNode));
		if (engineIconNode)
		{
			rv = mInner->Unassert(engine, kNC_StatusIcon, engineIconNode);
		}

		mInner->Assert(engine, kNC_loading, kTrueLiteral, PR_TRUE);
	}

	return(rv);
}

nsresult
InternetSearchDataSource::SaveEngineInfoIntoGraph(nsIFile *file, nsIFile *icon,
                                                  const PRUnichar *categoryHint,
                                                  const PRUnichar *dataUni,
                                                  PRBool isSystemSearchFile)
{
	nsresult			rv = NS_OK;

	if (!file && !icon)	return(NS_ERROR_UNEXPECTED);

	nsCOMPtr<nsIRDFResource>	searchRes;
	nsCOMPtr<nsIRDFResource>	categoryRes;
	nsCOMPtr<nsIFile>		native;

	if (icon != nsnull)
	{
		native = icon;
	}

	if (file != nsnull)
	{
		native = file;
	}

  PRBool exists;
  rv = native->Exists(&exists);
	if (NS_FAILED(rv)) return(rv);
	if (!exists) return(NS_ERROR_UNEXPECTED);

	nsAutoString basename;
	rv = native->GetLeafName(basename);
	if (NS_FAILED(rv)) return rv;

	// ensure that the basename points to the search engine file
	PRInt32		extensionOffset;
	if ((extensionOffset = basename.RFindChar(PRUnichar('.'))) > 0)
	{
		basename.Truncate(extensionOffset);
		basename.AppendLiteral(".src");
	}

  nsCAutoString filePath;
  rv = native->GetNativePath(filePath);
  if (NS_FAILED(rv)) return rv;
  
	nsAutoString	searchURL;
	searchURL.AssignASCII(kEngineProtocol);
	char		*uriCescaped = nsEscape(filePath.get(), url_Path);
	if (!uriCescaped)	return(NS_ERROR_NULL_POINTER);
	searchURL.AppendASCII(uriCescaped);
	nsCRT::free(uriCescaped);

	if ((extensionOffset = searchURL.RFindChar(PRUnichar('.'))) > 0)
	{
		searchURL.Truncate(extensionOffset);
		searchURL.AppendLiteral(".src");
	}

	if (NS_FAILED(rv = gRDFService->GetUnicodeResource(searchURL,
		getter_AddRefs(searchRes))))	return(rv);

	// save the basename reference
	if (!basename.IsEmpty())
	{
		basename.Insert(NS_ConvertASCIItoUTF16(kURINC_SearchCategoryEngineBasenamePrefix), 0);

		if (NS_FAILED(rv = gRDFService->GetUnicodeResource(basename,
			getter_AddRefs(categoryRes))))	return(rv);

		nsCOMPtr<nsIRDFLiteral>	searchLiteral;
		if (NS_SUCCEEDED(rv = gRDFService->GetLiteral(basename.get(),
				getter_AddRefs(searchLiteral))))
		{
			if (file)
			{
				updateAtom(mInner, searchRes, kNC_URL, searchLiteral, nsnull);
			}
		}
	}

	if (!searchRes)		return(NS_ERROR_UNEXPECTED);
	if (!categoryRes)	return(NS_ERROR_UNEXPECTED);

	nsAutoString	iconURL;
	if (icon)
	{
		nsCAutoString iconFileURL;
		if (NS_FAILED(rv = NS_GetURLSpecFromFile(icon, iconFileURL)))
			return(rv);
		AppendUTF8toUTF16(iconFileURL, iconURL);
	}

	// save icon url (if we have one)
	if (iconURL.Length() > 0)
	{
		nsCOMPtr<nsIRDFLiteral>	iconLiteral;
		if (NS_SUCCEEDED(rv = gRDFService->GetLiteral(iconURL.get(),
				getter_AddRefs(iconLiteral))))
		{
			updateAtom(mInner, searchRes, kNC_Icon, iconLiteral, nsnull);
		}
	}

  if (!isSystemSearchFile)
	{
		// mark our private search files, so that we can distinguish
		// between ours and any that are included with the OS
		updateAtom(mInner, searchRes, kNC_SearchType, kNC_Engine, nsnull);
	}

	if (dataUni != nsnull)
	{
		updateDataHintsInGraph(searchRes, dataUni);

		// finally, if we have a category hint, add this new engine into the category (if it exists)
		if (categoryHint && categoryDataSource)
		{
			nsCOMPtr<nsIRDFLiteral>	catLiteral;
			rv = gRDFService->GetLiteral(categoryHint, getter_AddRefs(catLiteral));

			nsCOMPtr<nsIRDFResource>	catSrc;
			if (catLiteral)
			{
				rv = categoryDataSource->GetSource(kNC_Title, catLiteral,
					PR_TRUE, getter_AddRefs(catSrc));
			}

			const char		*catURI = nsnull;
			if (catSrc)					
			{
				rv = catSrc->GetValueConst(&catURI);
			}

			nsCOMPtr<nsIRDFResource>	catRes;
			if (catURI)
			{
				nsAutoString	catList;
				catList.AssignASCII(kURINC_SearchCategoryPrefix);
				catList.AppendWithConversion(catURI);
				gRDFService->GetUnicodeResource(catList, getter_AddRefs(catRes));
			}

			nsCOMPtr<nsIRDFContainer> container;
			if (catRes)
			{
				container = do_CreateInstance(kRDFContainerCID, &rv);
			}
			if (container)
			{
				rv = container->Init(categoryDataSource, catRes);
				if (NS_SUCCEEDED(rv))
				{
					rv = gRDFC->MakeSeq(categoryDataSource, catRes, nsnull);
				}
				if (NS_SUCCEEDED(rv))
				{
					PRInt32		searchIndex = -1;
					if (NS_SUCCEEDED(rv = container->IndexOf(categoryRes, &searchIndex))
						&& (searchIndex < 0))
					{
						rv = container->AppendElement(categoryRes);
					}
				}
				if (NS_SUCCEEDED(rv))
				{
					// flush categoryDataSource
					nsCOMPtr<nsIRDFRemoteDataSource>	remoteCategoryStore;
					remoteCategoryStore = do_QueryInterface(categoryDataSource);
					if (remoteCategoryStore)
					{
						remoteCategoryStore->Flush();
					}
				}
			}
		}
	}

	// Note: add the child relationship last
	PRBool	hasChildFlag = PR_FALSE;
  rv = mInner->HasAssertion(kNC_SearchEngineRoot, kNC_Child, searchRes,
                            PR_TRUE, &hasChildFlag);
  if (NS_SUCCEEDED(rv) && !hasChildFlag)
	{
		mInner->Assert(kNC_SearchEngineRoot, kNC_Child, searchRes, PR_TRUE);
	}

	return(NS_OK);
}

nsresult
InternetSearchDataSource::GetSearchEngineList(nsIFile *searchDir,
              PRBool isSystemSearchFile)
{
        nsresult			rv = NS_OK;

    if (!mInner)
    {
    	return(NS_RDF_NO_VALUE);
    }

    PRBool hasMore = PR_FALSE;
    nsCOMPtr<nsISimpleEnumerator> dirIterator;
    rv = searchDir->GetDirectoryEntries(getter_AddRefs(dirIterator));
    if (NS_FAILED(rv)) return rv;

    nsCOMPtr<nsIFile> dirEntry;
	while ((rv = dirIterator->HasMoreElements(&hasMore)) == NS_OK && hasMore)
	{
		rv = dirIterator->GetNext((nsISupports**)getter_AddRefs(dirEntry));
        if (NS_FAILED(rv))
          continue;

        // Ignore hidden files/directories
        PRBool isHidden;
        rv = dirEntry->IsHidden(&isHidden);
        if (NS_FAILED(rv) || isHidden)
          continue;

        PRBool isDirectory;
        rv = dirEntry->IsDirectory(&isDirectory);
        if (NS_FAILED(rv))
          continue;
        if (isDirectory)
        {
          GetSearchEngineList(dirEntry, isSystemSearchFile);
          continue;
        }

        // Skip over empty files
        // Note: use GetFileSize even on Mac [instead of GetFileSizeWithResFork()]
        // as we want the size of ONLY the data fork
        PRInt64 fileSize;
        rv = dirEntry->GetFileSize(&fileSize);
        if (NS_FAILED(rv) || (fileSize == 0))
            continue;
    
        nsAutoString uri;
        rv = dirEntry->GetPath(uri);
        if (NS_FAILED(rv))
        continue;

		PRInt32		len = uri.Length();
		if (len < 5)
		{
			continue;
		}

		// check the extension (must be ".src")
		nsAutoString	extension;
		if ((uri.Right(extension, 4) != 4) || (!extension.LowerCaseEqualsLiteral(".src")))
		{
			continue;
		}

		// check for various icons
		PRBool		foundIconFlag = PR_FALSE;
		nsAutoString	temp;
		
		nsCOMPtr<nsILocalFile> iconFile, loopFile;

                static const char *extensions[] = {
                        ".gif",
                        ".jpg",
                        ".jpeg",
                        ".png",
                        nsnull,
                };

                for (int ext_count = 0; extensions[ext_count] != nsnull; ext_count++) {
                        temp = Substring(uri, 0, uri.Length()-4);
                        temp.Append(NS_ConvertASCIItoUCS2(extensions[ext_count]));
                        rv = NS_NewLocalFile(temp, PR_TRUE, getter_AddRefs(loopFile));
                        if (NS_FAILED(rv)) return rv;
                        rv = loopFile->Exists(&foundIconFlag);
                        if (NS_FAILED(rv)) return rv;
                        if (!foundIconFlag) continue;
                        rv = loopFile->IsFile(&foundIconFlag);
                        if (NS_FAILED(rv)) return rv;
                        if (foundIconFlag) 
                        {
                                iconFile = loopFile;
                                break;
                        } 
                }
		
		SaveEngineInfoIntoGraph(dirEntry, iconFile, nsnull, nsnull, isSystemSearchFile);
	}

#ifdef MOZ_PHOENIX
    if (!gReorderedEngineList)
      ReorderEngineList();
#endif
	return(rv);
}

nsresult
InternetSearchDataSource::ReadFileContents(nsILocalFile *localFile, nsString& sourceContents)
{
	nsresult			rv = NS_ERROR_FAILURE;
	PRInt64				contentsLen, total = 0;
	char				*contents;

        NS_ENSURE_ARG_POINTER(localFile);

        sourceContents.Truncate();

        rv = localFile->GetFileSize(&contentsLen);
        if (NS_FAILED(rv)) return rv;
        if (contentsLen > 0)
        {
                contents = new char [contentsLen + 1];
                if (contents)
                {
                        nsCOMPtr<nsIInputStream> inputStream;
                        rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), localFile);
                        if (NS_FAILED(rv)) {
                        	delete [] contents;
                        	return rv;
                        }
                        PRUint32 howMany;
                        while (total < contentsLen) {
                                rv = inputStream->Read(contents+total, 
                                                       PRUint32(contentsLen),
                                                       &howMany);
                                if (NS_FAILED(rv)) {
                                        delete [] contents;
                                        return rv;
                                }
                                total += howMany;
                        }
                        if (total == contentsLen)
		        {
				contents[contentsLen] = '\0';
				sourceContents.AssignWithConversion(contents, contentsLen);
				rv = NS_OK;
		        }
	        	delete [] contents;
	        	contents = nsnull;
		}
	}
	return(rv);
}



nsresult
InternetSearchDataSource::GetNumInterpretSections(const PRUnichar *dataUni, PRUint32 &numInterpretSections)
{
	numInterpretSections = 0;

	nsString	buffer(dataUni);

	NS_NAMED_LITERAL_STRING(section, "<interpret");
	PRBool		inSection = PR_FALSE;

	while(!buffer.IsEmpty())
	{
		PRInt32 eol = buffer.FindCharInSet("\r\n", 0);
		if (eol < 0)	break;
		nsAutoString	line;
		if (eol > 0)
		{
			buffer.Left(line, eol);
		}
		buffer.Cut(0, eol+1);
		if (line.IsEmpty())	continue;		// skip empty lines
		if (line[0] == PRUnichar('#'))	continue;	// skip comments
		line.Trim(" \t");
    if (!inSection)
		{
			PRInt32	sectionOffset = nsString_Find(section, line, PR_TRUE);
			if (sectionOffset < 0)	continue;
			line.Cut(0, sectionOffset + section.Length() + 1);
			inSection = PR_TRUE;
			++numInterpretSections;			// increment # of sections
		}
		line.Trim(" \t");
		PRInt32	len = line.Length();
		if (len > 0)
		{
			if (line[len-1] == PRUnichar('>'))
			{
				inSection = PR_FALSE;
				line.SetLength(len-1);
			}
		}
	}
	return(NS_OK);
}


nsresult
InternetSearchDataSource::GetData(const PRUnichar *dataUni, const char *sectionToFind, PRUint32 sectionNum,
				  const char *attribToFind, nsString &value)
{
	nsString	buffer(dataUni);

	nsresult	rv = NS_RDF_NO_VALUE;
	PRBool		inSection = PR_FALSE;

	nsAutoString	section;
	section.AssignLiteral("<");
	section.AppendWithConversion(sectionToFind);

	while(!buffer.IsEmpty())
	{
		PRInt32 eol = buffer.FindCharInSet("\r\n", 0);
		if (eol < 0)	break;
		nsAutoString	line;
		if (eol > 0)
		{
			buffer.Left(line, eol);
		}
		buffer.Cut(0, eol+1);
		if (line.IsEmpty())	continue;		// skip empty lines
		if (line[0] == PRUnichar('#'))	continue;	// skip comments
		line.Trim(" \t");
    if (!inSection)
		{
			PRInt32	sectionOffset = nsString_Find(section, line, PR_TRUE);
			if (sectionOffset < 0)	continue;
			if (sectionNum > 0)
			{
				--sectionNum;
				continue;
			}
			line.Cut(0, sectionOffset + section.Length() + 1);
			inSection = PR_TRUE;
		}
		line.Trim(" \t");
		PRInt32	len = line.Length();
		if (len > 0)
		{
			if (line[len-1] == PRUnichar('>'))
			{
				inSection = PR_FALSE;
				line.SetLength(len-1);
			}
		}
		PRInt32 equal = line.FindChar(PRUnichar('='));
		if (equal < 0)	continue;			// skip lines with no equality
		
		nsAutoString	attrib;
		if (equal > 0)
		{
			line.Left(attrib, equal /* - 1 */);
		}
		attrib.Trim(" \t");
		if (attrib.EqualsIgnoreCase(attribToFind))
		{
			line.Cut(0, equal+1);
			line.Trim(" \t");
			value = line;

			// strip of any enclosing quotes and trailing comments
			if ((value[0] == PRUnichar('\"')) || (value[0] == PRUnichar('\'')))
			{
				PRUnichar quoteChar = value[0];
				value.Cut(0,1);
				if (!value.IsEmpty())
				{
					PRInt32 quoteEnd = value.FindChar(quoteChar);
					if (quoteEnd >= 0)
					{
						value.Truncate(quoteEnd);
					}
				}
			}
			else
			{
				PRInt32 commentOffset = value.FindCharInSet("# \t", 0);
				if (commentOffset >= 0)
				{
					value.Truncate(commentOffset);
				}
				value.Trim(" \t");
			}
			rv = NS_OK;
			break;
		}
	}
	return(rv);
}



nsresult
InternetSearchDataSource::GetInputs(const PRUnichar *dataUni, nsString &engineName, nsString &userVar,
  const nsString &text, nsString &input, PRInt16 direction, PRUint16 pageNumber,  PRUint16 *whichButtons)
{
	nsString	buffer(dataUni);

	nsresult	rv = NS_OK;
	PRBool		inSection = PR_FALSE;
  PRBool    inDirInput; // directional input: "inputnext" or "inputprev"
  PRBool    foundInput = PR_FALSE;

	while(!buffer.IsEmpty())
	{
		PRInt32 eol = buffer.FindCharInSet("\r\n", 0);
		if (eol < 0)	break;
		nsAutoString	line;
		if (eol > 0)
		{
			buffer.Left(line, eol);
		}
		buffer.Cut(0, eol+1);
		if (line.IsEmpty())	continue;		// skip empty lines
		if (line[0] == PRUnichar('#'))	continue;	// skip comments
		line.Trim(" \t");
    if (!inSection)
		{
			if (line[0] != PRUnichar('<'))	continue;
			line.Cut(0, 1);
			inSection = PR_TRUE;
		}
		PRInt32	len = line.Length();
		if (len > 0)
		{
			if (line[len-1] == PRUnichar('>'))
			{
				inSection = PR_FALSE;
				line.SetLength(len-1);
			}
		}
    if (inSection)
      continue;

		// look for inputs
		if (line.Find("input", PR_TRUE) == 0)
		{
      line.Cut(0, 5);

      // look for "inputnext" or "inputprev"
      inDirInput = PR_FALSE;

      if (line.Find("next", PR_TRUE) == 0)
      {
        inDirInput = PR_TRUE;
        if (whichButtons)
          *whichButtons |= kHaveNext;
      }

      if (line.Find("prev", PR_TRUE) == 0)
      {
        inDirInput = PR_TRUE;
        if (whichButtons)
          *whichButtons |= kHavePrev;
      }

      if (inDirInput)
        line.Cut(0, 4);
      
			line.Trim(" \t");
			
			// first look for name attribute
			nsAutoString	nameAttrib;

			PRInt32	nameOffset = line.Find("name", PR_TRUE);
			if (nameOffset >= 0)
			{
				PRInt32 equal = line.FindChar(PRUnichar('='), nameOffset);
				if (equal >= 0)
				{
					PRInt32	startQuote = line.FindChar(PRUnichar('\"'), equal + 1);
					if (startQuote >= 0)
					{
						PRInt32	endQuote = line.FindChar(PRUnichar('\"'), startQuote + 1);
						if (endQuote > 0)
						{
							line.Mid(nameAttrib, startQuote+1, endQuote-startQuote-1);
							line.Cut(0, endQuote + 1);
						}
					}
					else
					{
						nameAttrib = line;
						nameAttrib.Cut(0, equal+1);
						nameAttrib.Trim(" \t");
						PRInt32 space = nameAttrib.FindCharInSet(" \t", 0);
						if (space > 0)
						{
							nameAttrib.Truncate(space);
							line.Cut(0, equal+1+space);
						}
						else
						{
							line.Truncate();
						}
					}
				}
			}
      if (foundInput && nameAttrib.IsEmpty()) 
        continue;

			// first look for value attribute
			nsAutoString	valueAttrib;

      PRInt32  valueOffset;
      if (!inDirInput)
        valueOffset = line.Find("value", PR_TRUE);
      else
        valueOffset = line.Find("factor", PR_TRUE);
			if (valueOffset >= 0)
			{
				PRInt32 equal = line.FindChar(PRUnichar('='), valueOffset);
				if (equal >= 0)
				{
					PRInt32	startQuote = line.FindChar(PRUnichar('\"'), equal + 1);
					if (startQuote >= 0)
					{
						PRInt32	endQuote = line.FindChar(PRUnichar('\"'), startQuote + 1);
						if (endQuote >= 0)
						{
							line.Mid(valueAttrib, startQuote+1, endQuote-startQuote-1);
						}
					}
					else
					{
						// if value attribute's "value" isn't quoted, get the first word... ?
						valueAttrib = line;
						valueAttrib.Cut(0, equal+1);
						valueAttrib.Trim(" \t");
						PRInt32 space = valueAttrib.FindCharInSet(" \t>", 0);
						if (space > 0)
						{
							valueAttrib.Truncate(space);
						}
					}
				}
			}
			else if (line.Find("user", PR_TRUE) >= 0)
			{
				userVar = nameAttrib;
				valueAttrib.Assign(text);
			}
			
			// XXX should ignore if  mode=browser  is specified
			// XXX need to do this better
			if (line.RFind("mode=browser", PR_TRUE) >= 0)
				continue;

			if (!valueAttrib.IsEmpty())
			{
        // Here's how we construct the input string:
        // <input> is first: Name Attr: Prefix      Data           Example:
        // YES               EMPTY      None        <value>        ACTION<value>
        // YES               NON-EMPTY  ?           <name>=<value> ACTION?<name>=<value>
        // NO                EMPTY      ----------- <ignored> -------------
        // NO                NON-EMPTY  &           <name>=<value> ACTION?<n1>=<v1>&<n2>=<v2>
				if (!input.IsEmpty())
					input.AppendLiteral("&");
        else if (!nameAttrib.IsEmpty()) 
          input.AppendLiteral("?");

        if (!nameAttrib.IsEmpty()) 
        {
				  input += nameAttrib;
          input.AppendLiteral("=");
        }

        // Indicate that we've already found an input, so we cannot have any 
        // inputs after this that do not have names. I could be more sophisticated
        // than this but I don't care right now since I'm only doing this for one
        // plugin. --ben
        foundInput = PR_TRUE;

        if (!inDirInput)
          input += valueAttrib;
        else
          input.AppendInt( computeIndex(valueAttrib, pageNumber, direction) );
			}
		}
	}

  // Now add the default vs. non-default parameters, which come from 
  // preferences and are part of the pre-configuration.
  nsCOMPtr<nsIPrefService> pserv(do_QueryInterface(prefs));
  if (pserv) 
  {
    // If the pref "browser.search.defaultenginename" has a user value, that
    // means the user has changed the engine in the list. This implies that
    // the selected engine was _not_ configured as the default by the 
    // distributor, because if the value of the pref matched, prefHasUserValue
    // would return false. 
    PRBool engineIsNotDefault = PR_FALSE;
    nsCOMPtr<nsIPrefBranch> rootBranch(do_QueryInterface(pserv));
    nsCOMPtr<nsIPrefBranch> defaultBranch;
    pserv->GetDefaultBranch("", getter_AddRefs(defaultBranch));

    if (defaultBranch) 
    {
      nsXPIDLString defaultEngineNameStr;
      nsCOMPtr<nsIPrefLocalizedString> defaultEngineName;
      rv = defaultBranch->GetComplexValue("browser.search.defaultenginename", 
                                          NS_GET_IID(nsIPrefLocalizedString),
                                          getter_AddRefs(defaultEngineName));
      if (NS_SUCCEEDED(rv)) {
        defaultEngineName->GetData(getter_Copies(defaultEngineNameStr));

        nsXPIDLString selectedEngineNameStr;
        nsCOMPtr<nsIPrefLocalizedString> selectedEngineName;
        rv = rootBranch->GetComplexValue("browser.search.selectedEngine", 
                                         NS_GET_IID(nsIPrefLocalizedString),
                                         getter_AddRefs(selectedEngineName));
        if (NS_SUCCEEDED(rv) && selectedEngineName) {
          selectedEngineName->GetData(getter_Copies(selectedEngineNameStr));
          engineIsNotDefault = !defaultEngineNameStr.Equals(selectedEngineNameStr);
        }
        else {
          engineIsNotDefault = PR_FALSE; // The selected engine *is* the default
                                         // since the user has not changed the
                                         // selected item in the list causing
                                         // the selectedEngine pref to be set.
        }
      }
    }

    PRInt32 i = 0;
    char prefNameBuf[1096];
    do
    {
      ++i;
      sprintf(prefNameBuf, "browser.search.param.%s.%d.", 
              NS_ConvertUCS2toUTF8(engineName).get(), i);

      nsCOMPtr<nsIPrefBranch> pb;
      rv = pserv->GetBranch(prefNameBuf, getter_AddRefs(pb));
      if (NS_FAILED(rv)) 
        break;

      nsCOMPtr<nsIPrefLocalizedString> parameter;
      nsXPIDLString parameterStr;
      rv = pb->GetComplexValue(engineIsNotDefault ? "custom" : "default", 
                               NS_GET_IID(nsIPrefLocalizedString), 
                               getter_AddRefs(parameter));
      if (NS_FAILED(rv))
        break;

      parameter->GetData(getter_Copies(parameterStr));
      
      if (!parameterStr.IsEmpty()) 
      {
        if (!input.IsEmpty())
          input.Append(NS_LITERAL_STRING("&"));
        input += parameterStr;
      }
    }
    while (1);

    // Now add the release identifier
    nsCOMPtr<nsIStringBundleService> stringService(do_GetService(NS_STRINGBUNDLE_CONTRACTID));
    nsCOMPtr<nsIStringBundle> bundle;
    rv = stringService->CreateBundle(SEARCHCONFIG_PROPERTIES, getter_AddRefs(bundle));
    nsCOMPtr<nsIStringBundle> intlBundle;
    rv = stringService->CreateBundle(INTL_PROPERTIES, getter_AddRefs(intlBundle));

    nsXPIDLString langName;
    intlBundle->GetStringFromName(NS_LITERAL_STRING("general.useragent.locale").get(), 
                                  getter_Copies(langName));

    nsAutoString keyTemplate(NS_LITERAL_STRING("browser.search.param."));
    keyTemplate += engineName;
    keyTemplate.Append(NS_LITERAL_STRING(".release"));

    nsXPIDLString releaseValue;
	  NS_NAMED_LITERAL_STRING(distributionID, MOZ_DISTRIBUTION_ID);
    const PRUnichar* strings[] = { distributionID.get(), langName.get() };
    bundle->FormatStringFromName(keyTemplate.get(), strings, 2, getter_Copies(releaseValue));

    if (!releaseValue.IsEmpty()) 
    {
      if (!input.IsEmpty())
        input.Append(NS_LITERAL_STRING("&"));
      input += releaseValue;
    }

    // Now add order parameters.
    nsCOMPtr<nsIPrefBranch> pb;
    rv = pserv->GetBranch("", getter_AddRefs(pb));
    if (NS_FAILED(rv)) return rv;

    i = 0;
    do {
      ++i;
      sprintf(prefNameBuf, "browser.search.order.%d", i);

      nsCOMPtr<nsIPrefLocalizedString> orderEngineName;
      rv = pb->GetComplexValue(prefNameBuf, 
                               NS_GET_IID(nsIPrefLocalizedString),
                               getter_AddRefs(orderEngineName));
      if (NS_FAILED(rv)) 
        break;

      nsXPIDLString orderEngineNameStr;
      orderEngineName->GetData(getter_Copies(orderEngineNameStr));
      if (orderEngineNameStr.Equals(engineName))
        break;
    }
    while (PR_TRUE);

    if (NS_SUCCEEDED(rv))
    {
      sprintf(prefNameBuf, "browser.search.order.%s.%d",
              NS_ConvertUCS2toUTF8(engineName).get(), i);
      
      nsCOMPtr<nsIPrefLocalizedString> orderParam;
      rv = rootBranch->GetComplexValue(prefNameBuf, 
                                       NS_GET_IID(nsIPrefLocalizedString),
                                       getter_AddRefs(orderParam));
      if (NS_FAILED(rv))
      {
        sprintf(prefNameBuf, "browser.search.order.%s",
                NS_ConvertUCS2toUTF8(engineName).get());
        rv = rootBranch->GetComplexValue(prefNameBuf, 
                                         NS_GET_IID(nsIPrefLocalizedString),
                                         getter_AddRefs(orderParam));
      }
    
      if (NS_SUCCEEDED(rv)) 
      {
        nsXPIDLString orderParamStr;
        orderParam->GetData(getter_Copies(orderParamStr));

        if (!orderParamStr.IsEmpty())
        {
          if (!input.IsEmpty())
            input.Append(NS_LITERAL_STRING("&"));
          input += orderParamStr;
        }
      }
    }

    rv = NS_OK;
  }

	return(rv);
}

PRInt32
InternetSearchDataSource::computeIndex(nsAutoString &factor, 
                                       PRUint16 page, PRInt16 direction)
{
  // XXX get page
  PRInt32 errorCode, index = 0;
  PRInt32 factorInt = factor.ToInteger(&errorCode);
  
  if (NS_SUCCEEDED(errorCode))
  {
    // if factor is garbled assume 10
    if (factorInt <= 0)
      factorInt = 10;

    if (direction < 0)
    {
      // don't pass back a negative index!
      if (0 <= (page - 1))
        --page;
    }
    index = factorInt * page;
  }

  return index;
}



nsresult
InternetSearchDataSource::GetURL(nsIRDFResource *source, nsIRDFLiteral** aResult)
{
        const char	*uri = nsnull;
	source->GetValueConst( &uri );
	nsAutoString	url;
	url.AssignWithConversion(uri);
	nsIRDFLiteral	*literal;
	gRDFService->GetLiteral(url.get(), &literal);
        *aResult = literal;
        return NS_OK;
}



// stream observer methods



NS_IMETHODIMP
InternetSearchDataSource::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
{
#ifdef	DEBUG_SEARCH_OUTPUT
	printf("InternetSearchDataSourceCallback::OnStartRequest entered.\n");
#endif
	return(NS_OK);
}



NS_IMETHODIMP
InternetSearchDataSource::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
				nsIInputStream *aIStream, PRUint32 sourceOffset, PRUint32 aLength)
{
	if (!ctxt)	return(NS_ERROR_NO_INTERFACE);
	nsCOMPtr<nsIInternetSearchContext> context (do_QueryInterface(ctxt));
	if (!context)	return(NS_ERROR_NO_INTERFACE);

	nsresult	rv = NS_OK;

	if (aLength < 1)	return(rv);

	PRUint32	count;
	char		*buffer = new char[ aLength ];
	if (!buffer)	return(NS_ERROR_OUT_OF_MEMORY);

	if (NS_FAILED(rv = aIStream->Read(buffer, aLength, &count)) || count == 0)
	{
#ifdef	DEBUG
		printf("Search datasource read failure.\n");
#endif
		delete []buffer;
		return(rv);
	}
	if (count != aLength)
	{
#ifdef	DEBUG
		printf("Search datasource read # of bytes failure.\n");
#endif
		delete []buffer;
		return(NS_ERROR_UNEXPECTED);
	}

	nsCOMPtr<nsIUnicodeDecoder>	decoder;
	context->GetUnicodeDecoder(getter_AddRefs(decoder));
	if (decoder)
	{
		char			*aBuffer = buffer;
		PRInt32			unicharBufLen = 0;
		decoder->GetMaxLength(aBuffer, aLength, &unicharBufLen);
		PRUnichar		*unichars = new PRUnichar [ unicharBufLen+1 ];
		do
		{
			PRInt32		srcLength = aLength;
			PRInt32		unicharLength = unicharBufLen;
			rv = decoder->Convert(aBuffer, &srcLength, unichars, &unicharLength);
			unichars[unicharLength]=0;  //add this since the unicode converters can't be trusted to do so.

			// Move the nsParser.cpp 00 -> space hack to here so it won't break UCS2 file

			// Hack Start
			for(PRInt32 i=0;i < unicharLength; i++)
			{
				if(0x0000 == unichars[i])
				{
					unichars[i] = PRUnichar(' ');
				}
			}
			// Hack End

			context->AppendUnicodeBytes(unichars, unicharLength);
			// if we failed, we consume one byte by replace it with U+FFFD
			// and try conversion again.
			if(NS_FAILED(rv))
			{
				decoder->Reset();
				unsigned char	smallBuf[2];
				smallBuf[0] = 0xFF;
				smallBuf[1] = 0xFD;
				context->AppendBytes( (const char *)&smallBuf, 2L);
				if(((PRUint32) (srcLength + 1)) > aLength)
					srcLength = aLength;
				else 
					srcLength++;
				aBuffer += srcLength;
				aLength -= srcLength;
			}
		} while (NS_FAILED(rv) && (aLength > 0));
		delete [] unichars;
		unichars = nsnull;
	}
	else
	{
		context->AppendBytes(buffer, aLength);
	}

	delete [] buffer;
	buffer = nsnull;
	return(rv);
}



NS_IMETHODIMP
InternetSearchDataSource::OnStopRequest(nsIRequest *request, nsISupports *ctxt,
                                        nsresult status)
{
	if (!mInner)	return(NS_OK);

  nsCOMPtr<nsIChannel> channel (do_QueryInterface(request));
	nsCOMPtr<nsIInternetSearchContext> context (do_QueryInterface(ctxt));
	if (!ctxt)	return(NS_ERROR_NO_INTERFACE);

	nsresult	rv;
	PRUint32	contextType = 0;
	if (NS_FAILED(rv = context->GetContextType(&contextType)))		return(rv);

	if (contextType == nsIInternetSearchContext::WEB_SEARCH_CONTEXT)
	{
		// continue to process nsIInternetSearchContext::WEB_SEARCH_CONTEXT
		rv = webSearchFinalize(channel, context);
	}
	else if (contextType == nsIInternetSearchContext::ENGINE_DOWNLOAD_NEW_CONTEXT ||
	         contextType == nsIInternetSearchContext::ICON_DOWNLOAD_NEW_CONTEXT ||
	         contextType == nsIInternetSearchContext::ENGINE_DOWNLOAD_UPDATE_CONTEXT ||
	         contextType == nsIInternetSearchContext::ICON_DOWNLOAD_UPDATE_CONTEXT)
	{
		nsCOMPtr<nsIHttpChannel> httpChannel (do_QueryInterface(channel));
		if (!httpChannel)	return(NS_ERROR_UNEXPECTED);

		// check HTTP status to ensure success
		PRUint32	httpStatus = 0;
		if (NS_SUCCEEDED(rv = httpChannel->GetResponseStatus(&httpStatus)) &&
			(httpStatus == 200))
		{
			rv = saveContents(channel, context, contextType);
		}
	}
	else if (contextType == nsIInternetSearchContext::ENGINE_UPDATE_HEAD_CONTEXT)
	{
		nsCOMPtr<nsIRDFResource>	theEngine;
		if (NS_FAILED(rv = context->GetEngine(getter_AddRefs(theEngine))))	return(rv);
		if (!theEngine)	return(NS_ERROR_NO_INTERFACE);

#ifdef	DEBUG_SEARCH_UPDATES
		const char	*engineURI = nsnull;
		theEngine->GetValueConst(&engineURI);
#endif

		// free up "busy" info now & mark as non-busy
		busySchedule = PR_FALSE;
		busyResource = nsnull;

		// mark now as the last time we stat'ted the search engine
		// regardless of HTTP status
		rv = validateEngineNow(theEngine);
		NS_ENSURE_SUCCESS(rv, rv);

		// we only have HTTP "HEAD" information when doing updates
		nsCOMPtr<nsIHttpChannel> httpChannel (do_QueryInterface(channel));
		if (!httpChannel)	return(NS_ERROR_UNEXPECTED);

		// check HTTP status to ensure success
		PRUint32	httpStatus = 0;
		if (NS_FAILED(rv = httpChannel->GetResponseStatus(&httpStatus)))
			return(rv);
		if (httpStatus != 200)	return(NS_ERROR_UNEXPECTED);

		// get last-modified & content-length info
		nsCAutoString			lastModValue, contentLengthValue;

        if (NS_FAILED(httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Last-Modified"), lastModValue)))
            lastModValue.Truncate();
        if (NS_FAILED(httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Length"), contentLengthValue)))
            contentLengthValue.Truncate();

		// should we fetch the entire file?
		PRBool		updateSearchEngineFile = PR_FALSE;

		// save out new search engine state data into localstore
		PRBool			tempDirty = PR_FALSE;
		nsCOMPtr<nsIRDFLiteral>	newValue;
		if (!lastModValue.IsEmpty())
		{
			gRDFService->GetLiteral(NS_ConvertASCIItoUCS2(lastModValue).get(),
				getter_AddRefs(newValue));
			if (newValue)
			{
				updateAtom(mLocalstore, theEngine, kWEB_LastPingModDate, newValue,
					&tempDirty);
        if (tempDirty)
          updateSearchEngineFile = PR_TRUE;
			}
		}
		if (!contentLengthValue.IsEmpty())
		{
			gRDFService->GetLiteral(NS_ConvertASCIItoUCS2(contentLengthValue).get(),
				getter_AddRefs(newValue));
			if (newValue)
			{
				updateAtom(mLocalstore, theEngine, kWEB_LastPingContentLen, newValue,
					&tempDirty);
        if (tempDirty)
				{
					updateSearchEngineFile = PR_TRUE;
				}
				else
				{
					// worst case: compare local data size vs. remote content size
					nsCOMPtr<nsIRDFLiteral>	dataLit;
					if (NS_SUCCEEDED(rv = FindData(theEngine, getter_AddRefs(dataLit))) &&
						(rv != NS_RDF_NO_VALUE))
					{
						const PRUnichar	*dataUni = nsnull;
						dataLit->GetValueConst(&dataUni);
						nsAutoString	dataStr(dataUni);
						PRInt32		dataLen=dataStr.Length();
#ifdef	DEBUG_SEARCH_UPDATES
						printf("    Search engine='%s' data length='%d'\n", engineURI, dataLen);
#endif
						PRInt32	contentLen=0, err=0;
						contentLen = contentLengthValue.ToInteger(&err);
						if ((!err) && (dataLen != contentLen))
						{
#ifdef	DEBUG_SEARCH_UPDATES
							printf("    Search engine '%s' data length != remote content length, so update\n",
								engineURI, dataLen);
#endif
							updateSearchEngineFile = PR_TRUE;
						}
					}
				}
			}
		}

    if (updateSearchEngineFile)
		{
#ifdef	DEBUG_SEARCH_UPDATES
			printf("    Search engine='%s' needs updating, so fetching it\n", engineURI);
#endif
			// get update URL
			nsCString		updateURL;
			nsCOMPtr<nsIRDFNode>	aNode;
			if (NS_SUCCEEDED(rv = mInner->GetTarget(theEngine, kNC_Update, PR_TRUE, getter_AddRefs(aNode)))
				&& (rv != NS_RDF_NO_VALUE))
			{
				nsCOMPtr<nsIRDFLiteral>	aLiteral (do_QueryInterface(aNode));
				if (aLiteral)
				{
					const PRUnichar	*updateUni = nsnull;
					aLiteral->GetValueConst(&updateUni);
					if (updateUni)
					{
						updateURL.AssignWithConversion(updateUni);
					}
				}
			}

			// get update icon
			nsCString		updateIconURL;
			if (NS_SUCCEEDED(rv = mInner->GetTarget(theEngine, kNC_UpdateIcon, PR_TRUE, getter_AddRefs(aNode)))
				&& (rv != NS_RDF_NO_VALUE))
			{
				nsCOMPtr<nsIRDFLiteral>	aIconLiteral (do_QueryInterface(aNode));
				if (aIconLiteral)
				{
					const PRUnichar	*updateIconUni = nsnull;
					aIconLiteral->GetValueConst(&updateIconUni);
					if (updateIconUni)
					{
						updateIconURL.AssignWithConversion(updateIconUni);
					}
				}
			}

			// download it!
      AddSearchEngineInternal(updateURL.get(), updateIconURL.get(),
                              nsnull, nsnull, theEngine);
		}
		else
		{
#ifdef	DEBUG_SEARCH_UPDATES
			printf("    Search engine='%s' is current and requires no updating\n", engineURI);
#endif
		}
	}
	else
	{
		rv = NS_ERROR_UNEXPECTED;
	}
	return(rv);
}



nsresult
InternetSearchDataSource::validateEngineNow(nsIRDFResource *engine)
{
	// get the current date/time [from microseconds (PRTime) to seconds]
	// also, need to convert from 64 bites to 32 bites as we don't have an easy
	// way to convert from 64 bit number to a string like we do for 32 bit numbers
	PRTime		now64 = PR_Now(), temp64, million;
	LL_I2L(million, PR_USEC_PER_SEC);
	LL_DIV(temp64, now64, million);
	PRInt32		now32;
	LL_L2I(now32, temp64);

	// validate this engine as of now (save as string
	// literal as that's all RDF can currently serialize)
	nsAutoString	nowStr;
	nowStr.AppendInt(now32);

	nsresult		rv;
	nsCOMPtr<nsIRDFLiteral>	nowLiteral;
	if (NS_FAILED(rv = gRDFService->GetLiteral(nowStr.get(),
			getter_AddRefs(nowLiteral))))	return(rv);
	updateAtom(mLocalstore, engine, kWEB_LastPingDate, nowLiteral, nsnull);

	// flush localstore
	nsCOMPtr<nsIRDFRemoteDataSource> remoteLocalStore (do_QueryInterface(mLocalstore));
	if (remoteLocalStore)
	{
		remoteLocalStore->Flush();
	}
	return(NS_OK);
}



nsresult
InternetSearchDataSource::webSearchFinalize(nsIChannel* channel, nsIInternetSearchContext *context)
{
	nsresult			rv;
	nsCOMPtr<nsIRDFResource>	mParent;
	if (NS_FAILED(rv = context->GetParent(getter_AddRefs(mParent))))	return(rv);
//	Note: mParent can be null
//	if (!mParent)	return(NS_ERROR_NO_INTERFACE);

	nsCOMPtr<nsIRDFResource>	mEngine;
	if (NS_FAILED(rv = context->GetEngine(getter_AddRefs(mEngine))))	return(rv);
	if (!mEngine)	return(NS_ERROR_NO_INTERFACE);

	nsCOMPtr<nsIURI> aURL;
	rv = channel->GetURI(getter_AddRefs(aURL));
	if (NS_FAILED(rv)) return rv;

	// copy the engine's icon reference (if it has one) onto the result node
	nsCOMPtr<nsIRDFNode>		engineIconStatusNode = nsnull;
	mInner->GetTarget(mEngine, kNC_Icon, PR_TRUE, getter_AddRefs(engineIconStatusNode));
	if (engineIconStatusNode)
	{
		rv = mInner->Assert(mEngine, kNC_StatusIcon, engineIconStatusNode, PR_TRUE);
	}

	const PRUnichar	*uniBuf = nsnull;
	if (NS_SUCCEEDED(rv = context->GetBufferConst(&uniBuf)) && (uniBuf))
	{
		if (mParent && (gBrowserSearchMode>0))
		{
			// save HTML result page for this engine
			nsCOMPtr<nsIRDFLiteral>	htmlLiteral;
			if (NS_SUCCEEDED(rv = gRDFService->GetLiteral(uniBuf, getter_AddRefs(htmlLiteral))))
			{
				rv = mInner->Assert(mEngine, kNC_HTML, htmlLiteral, PR_TRUE);
			}
		}

		// parse up HTML results
		PRInt32 uniBufLen = 0L;
		if (NS_SUCCEEDED(rv = context->GetBufferLength(&uniBufLen)))
		{
			rv = ParseHTML(aURL, mParent, mEngine, uniBuf, uniBufLen);
		}
	}
	else
	{
#ifdef	DEBUG_SEARCH_OUTPUT
		printf(" *** InternetSearchDataSourceCallback::OnStopRequest:  no data.\n\n");
#endif
	}

	// after we're all done with the html buffer, get rid of it
	context->Truncate();

	// (do this last) potentially remove the loading attribute
	mInner->Unassert(mEngine, kNC_loading, kTrueLiteral);

	if (mLoadGroup)
	{
		PRUint32	count = 0;
		if (NS_SUCCEEDED(rv = mLoadGroup->GetActiveCount(&count)))
		{
			// is this the last connection in the loadgroup?
			if (count <= 1)
			{
				Stop();
			}
		}
	}

	return(NS_OK);
}



nsresult
InternetSearchDataSource::ParseHTML(nsIURI *aURL, nsIRDFResource *mParent,
				nsIRDFResource *mEngine, const PRUnichar *htmlPage, PRInt32 htmlPageSize)
{
	// get data out of graph
	nsresult	rv;
	nsCOMPtr<nsIRDFNode>	dataNode;
	if (NS_FAILED(rv = mInner->GetTarget(mEngine, kNC_Data, PR_TRUE, getter_AddRefs(dataNode))))
	{
		return(rv);
	}
	nsCOMPtr<nsIRDFLiteral>	dataLiteral (do_QueryInterface(dataNode));
	if (!dataLiteral)	return(NS_ERROR_NULL_POINTER);

	const PRUnichar	*dataUni = nsnull;
	if (NS_FAILED(rv = dataLiteral->GetValueConst(&dataUni)))
		return(rv);
	if (!dataUni)	return(NS_ERROR_NULL_POINTER);

	// get name of this engine once
	nsAutoString	engineStr;
	GetData(dataUni, "search", 0, "name", engineStr);

	// pre-compute host (we might discard URLs that match this)
	nsCAutoString hostName;
	aURL->GetAsciiHost(hostName);

	// pre-compute server path (we might discard URLs that match this)
	nsAutoString	serverPathStr;
	nsCAutoString serverPath;
	aURL->GetPath(serverPath);
	if (!serverPath.IsEmpty())
	{
        AppendUTF8toUTF16(serverPath, serverPathStr);
        serverPath.Truncate();

		PRInt32 serverOptionsOffset = serverPathStr.FindChar(PRUnichar('?'));
		if (serverOptionsOffset >= 0)	serverPathStr.Truncate(serverOptionsOffset);
	}

	PRBool		hasPriceFlag = PR_FALSE, hasAvailabilityFlag = PR_FALSE, hasRelevanceFlag = PR_FALSE;
	PRBool		hasDateFlag = PR_FALSE;
	PRBool		skipLocalFlag = PR_FALSE, useAllHREFsFlag = PR_FALSE;
	PRInt32		pageRank = 1;
	PRUint32	numInterpretSections, numResults = 0;

	GetNumInterpretSections(dataUni, numInterpretSections);
	if (numInterpretSections < 1)
	{
		// if no <interpret> sections, use all the HREFS on the page
		numInterpretSections = 1;
		useAllHREFsFlag = PR_TRUE;
		skipLocalFlag = PR_TRUE;
	}

#ifdef	DEBUG
	PRTime		now;
	now = PR_Now();
  printf("\nStart processing search results:   %u bytes \n", htmlPageSize); 
#endif

	// need to handle multiple <interpret> sections, per spec
	for (PRUint32 interpretSectionNum=0; interpretSectionNum < numInterpretSections; interpretSectionNum++)
	{
		nsAutoString	resultListStartStr, resultListEndStr;
		nsAutoString	resultItemStartStr, resultItemEndStr;
		nsAutoString	relevanceStartStr, relevanceEndStr;
		nsAutoString	bannerStartStr, bannerEndStr, skiplocalStr;
		nsAutoString	priceStartStr, priceEndStr, availStartStr, availEndStr;
		nsAutoString	dateStartStr, dateEndStr;
		nsAutoString	nameStartStr, nameEndStr;
		nsAutoString	emailStartStr, emailEndStr;
		nsAutoString	browserResultTypeStr;
		browserResultTypeStr.AssignLiteral("result");		// default to "result"

		// use a nsDependentString so that "htmlPage" data isn't copied
		nsDependentString  htmlResults(htmlPage, htmlPageSize);
		PRUint32           startIndex = 0L, stopIndex = htmlPageSize;

    if (!useAllHREFsFlag)
		{
			GetData(dataUni, "interpret", interpretSectionNum, "resultListStart", resultListStartStr);
			GetData(dataUni, "interpret", interpretSectionNum, "resultListEnd", resultListEndStr);
			GetData(dataUni, "interpret", interpretSectionNum, "resultItemStart", resultItemStartStr);
			GetData(dataUni, "interpret", interpretSectionNum, "resultItemEnd", resultItemEndStr);
			GetData(dataUni, "interpret", interpretSectionNum, "relevanceStart", relevanceStartStr);
			GetData(dataUni, "interpret", interpretSectionNum, "relevanceEnd", relevanceEndStr);
			GetData(dataUni, "interpret", interpretSectionNum, "bannerStart", bannerStartStr);
			GetData(dataUni, "interpret", interpretSectionNum, "bannerEnd", bannerEndStr);
			GetData(dataUni, "interpret", interpretSectionNum, "skiplocal", skiplocalStr);
			skipLocalFlag = (skiplocalStr.LowerCaseEqualsLiteral("true")) ? PR_TRUE : PR_FALSE;

			// shopping channel support
			GetData(dataUni, "interpret", interpretSectionNum, "priceStart", priceStartStr);
			GetData(dataUni, "interpret", interpretSectionNum, "priceEnd", priceEndStr);
			GetData(dataUni, "interpret", interpretSectionNum, "availStart", availStartStr);
			GetData(dataUni, "interpret", interpretSectionNum, "availEnd", availEndStr);

			// news channel support
			GetData(dataUni, "interpret", interpretSectionNum, "dateStart", dateStartStr);
			GetData(dataUni, "interpret", interpretSectionNum, "dateEnd", dateEndStr);

			// people channel support
			GetData(dataUni, "interpret", interpretSectionNum, "nameStart", nameStartStr);
			GetData(dataUni, "interpret", interpretSectionNum, "nameEnd", nameEndStr);
			GetData(dataUni, "interpret", interpretSectionNum, "emailStart", emailStartStr);
			GetData(dataUni, "interpret", interpretSectionNum, "emailEnd", emailEndStr);

			// special browser support
			GetData(dataUni, "interpret", interpretSectionNum, "browserResultType", browserResultTypeStr);
			if (browserResultTypeStr.IsEmpty())
			{
				browserResultTypeStr.AssignLiteral("result");	// default to "result"
			}
		}

		// look for banner
		nsCOMPtr<nsIRDFLiteral>	bannerLiteral;
		if ((!bannerStartStr.IsEmpty()) && (!bannerEndStr.IsEmpty()))
		{
			PRInt32	bannerStart = nsString_Find(bannerStartStr, htmlResults, PR_TRUE);
			if (bannerStart >= 0)
			{
				startIndex = bannerStart;

				PRInt32	bannerEnd = nsString_Find(bannerEndStr, htmlResults, PR_TRUE, bannerStart + bannerStartStr.Length());
				if (bannerEnd > bannerStart)
				{
					stopIndex = bannerEnd - 1;

					nsAutoString	htmlBanner;
					htmlResults.Mid(htmlBanner, bannerStart, bannerEnd - bannerStart - 1);
					if (!htmlBanner.IsEmpty())
					{
						const PRUnichar	*bannerUni = htmlBanner.get();
						if (bannerUni)
						{
							gRDFService->GetLiteral(bannerUni, getter_AddRefs(bannerLiteral));
						}
					}
				}
			}
		}

		if (!resultListStartStr.IsEmpty())
		{
			PRInt32	resultListStart = nsString_Find(resultListStartStr, htmlResults, PR_TRUE);
			if (resultListStart >= 0)
			{
        startIndex = resultListStart + resultListStartStr.Length();
			}
      else if (!useAllHREFsFlag)
			{
				// if we have multiple <INTERPRET> sections but can't find the startIndex
				// of a result list block, just continue on with the the next block
				continue;
			}
		}
		if (!resultListEndStr.IsEmpty())
		{
			// rjc note: use RFind to find the LAST
			// occurrence of resultListEndStr

		    nsAString::const_iterator originalStart, start, end;
		    htmlResults.BeginReading(start);
		    htmlResults.EndReading(end);
		    originalStart = start;
		    
		    if (RFindInReadable(resultListEndStr, start, end))
			stopIndex = Distance(originalStart, start);
		}

		PRBool	trimItemStart = PR_TRUE;
		PRBool	trimItemEnd = PR_FALSE;		// rjc note: testing shows we should NEVER trim end???

		// if resultItemStartStr is not specified, try making it just be "HREF="
		if (resultItemStartStr.IsEmpty())
		{
			resultItemStartStr.AssignLiteral("HREF=");
			trimItemStart = PR_FALSE;
		}

		// if resultItemEndStr is not specified, try making it the same as resultItemStartStr
		if (resultItemEndStr.IsEmpty())
		{
			resultItemEndStr = resultItemStartStr;
			trimItemEnd = PR_TRUE;
		}

		while(startIndex < stopIndex)
		{
			PRInt32	resultItemStart;
			resultItemStart = nsString_Find(resultItemStartStr, htmlResults, PR_TRUE, startIndex);
			if (resultItemStart < 0)	break;

			PRInt32	resultItemEnd;
      if (trimItemStart)
			{
				resultItemStart += resultItemStartStr.Length();
				resultItemEnd = nsString_Find(resultItemEndStr, htmlResults, PR_TRUE, resultItemStart);
			}
			else
			{
				resultItemEnd = nsString_Find(resultItemEndStr, htmlResults, PR_TRUE, resultItemStart + resultItemStartStr.Length());
			}

			if (resultItemEnd < 0)
			{
				resultItemEnd = stopIndex;
			}
      else if (!trimItemEnd && resultItemEnd >= 0)
			{
				resultItemEnd += resultItemEndStr.Length();
			}

			// forced to use an nsAutoString (which copies)
			// as CBufDescriptor doesn't guarantee null terminator
			if (resultItemEnd - resultItemStart - 1 <= 0)    break;
			nsAutoString resultItem(&htmlPage[resultItemStart],
				resultItemEnd - resultItemStart - 1);

			if (resultItem.IsEmpty())	break;

			// advance startIndex here so that, from this point onward, "continue" can be used
			startIndex = resultItemEnd;

#ifdef	DEBUG_SEARCH_OUTPUT
			char	*results = ToNewCString(resultItem);
			if (results)
			{
				printf("\n----- Search result: '%s'\n\n", results);
				nsCRT::free(results);
				results = nsnull;
			}
#endif

			// look for href
			PRInt32	hrefOffset = resultItem.Find("HREF=", PR_TRUE);
			if (hrefOffset < 0)
			{
#ifdef	DEBUG_SEARCH_OUTPUT
				printf("\n***** Unable to find HREF!\n\n");
#endif
				continue;
			}

			nsAutoString	hrefStr;
			PRInt32		quoteStartOffset = resultItem.FindCharInSet("\"\'>", hrefOffset);
			PRInt32		quoteEndOffset;
			if (quoteStartOffset < hrefOffset)
			{
				// handle case where HREF isn't quoted
				quoteStartOffset = hrefOffset + strlen("HREF=");
				quoteEndOffset = resultItem.FindChar('>', quoteStartOffset);
				if (quoteEndOffset < quoteStartOffset)	continue;
			}
			else
			{
				if (resultItem[quoteStartOffset] == PRUnichar('>'))
				{
					// handle case where HREF isn't quoted
					quoteEndOffset = quoteStartOffset;
					quoteStartOffset = hrefOffset + strlen("HREF=") -1;
				}
				else
				{
					quoteEndOffset = resultItem.FindCharInSet("\"\'", quoteStartOffset + 1);
					if (quoteEndOffset < hrefOffset)	continue;
				}
			}
			resultItem.Mid(hrefStr, quoteStartOffset + 1, quoteEndOffset - quoteStartOffset - 1);

			ConvertEntities(hrefStr, PR_FALSE, PR_TRUE, PR_FALSE);

			if (hrefStr.IsEmpty())	continue;

			char		*absURIStr = nsnull;
			nsCAutoString	hrefstrC;
			hrefstrC.AssignWithConversion(hrefStr);

			if (NS_SUCCEEDED(rv = NS_MakeAbsoluteURI(&absURIStr, hrefstrC.get(), aURL))
			    && (absURIStr))
			{
				hrefStr.AssignWithConversion(absURIStr);

				nsCOMPtr<nsIURI>	absURI;
				rv = NS_NewURI(getter_AddRefs(absURI), absURIStr);
				nsCRT::free(absURIStr);
				absURIStr = nsnull;

        if (absURI && skipLocalFlag)
				{
					nsCAutoString absPath;
					absURI->GetPath(absPath);
					if (!absPath.IsEmpty())
					{
                        NS_ConvertUTF8toUCS2 absPathStr(absPath);
						PRInt32 pathOptionsOffset = absPathStr.FindChar(PRUnichar('?'));
						if (pathOptionsOffset >= 0)
							absPathStr.Truncate(pathOptionsOffset);
						PRBool	pathsMatchFlag = serverPathStr.Equals(absPathStr, nsCaseInsensitiveStringComparator());
            if (pathsMatchFlag)
              continue;
					}

					if (!hostName.IsEmpty())
					{
						nsCAutoString absHost;
						absURI->GetAsciiHost(absHost);
						if (!absHost.IsEmpty())
						{
							PRBool	hostsMatchFlag = !nsCRT::strcasecmp(hostName.get(), absHost.get());
              if (hostsMatchFlag)
                continue;
						}
					}
				}
			}

			// if this result is to be filtered out, notice it now
      if (isSearchResultFiltered(hrefStr))
				continue;

			nsAutoString	site(hrefStr);

#ifdef	DEBUG_SEARCH_OUTPUT
			char *hrefCStr = ToNewCString(hrefStr);
			if (hrefCStr)
			{
				printf("HREF: '%s'\n", hrefCStr);
				nsCRT::free(hrefCStr);
				hrefCStr = nsnull;
			}
#endif

			nsCOMPtr<nsIRDFResource>	res;

			// save HREF attribute as URL
			if (NS_SUCCEEDED(rv = gRDFService->GetAnonymousResource(getter_AddRefs(res))))
			{
				const PRUnichar	*hrefUni = hrefStr.get();
				if (hrefUni)
				{
					nsCOMPtr<nsIRDFLiteral>	hrefLiteral;
					if (NS_SUCCEEDED(rv = gRDFService->GetLiteral(hrefUni, getter_AddRefs(hrefLiteral))))
					{
						mInner->Assert(res, kNC_URL, hrefLiteral, PR_TRUE);
					}
				}
			}

			if (NS_FAILED(rv))	continue;
			
			// set HTML response chunk
			const PRUnichar	*htmlResponseUni = resultItem.get();
			if (htmlResponseUni && (gBrowserSearchMode>0))
			{
				nsCOMPtr<nsIRDFLiteral>	htmlResponseLiteral;
				if (NS_SUCCEEDED(rv = gRDFService->GetLiteral(htmlResponseUni, getter_AddRefs(htmlResponseLiteral))))
				{
					if (htmlResponseLiteral)
					{
						mInner->Assert(res, kNC_HTML, htmlResponseLiteral, PR_TRUE);
					}
				}
			}
			
			// set banner (if we have one)
			if (bannerLiteral)
			{
				mInner->Assert(res, kNC_Banner, bannerLiteral, PR_TRUE);
			}

			// look for Site (if it isn't already set)
			nsCOMPtr<nsIRDFNode>		oldSiteRes = nsnull;
			mInner->GetTarget(res, kNC_Site, PR_TRUE, getter_AddRefs(oldSiteRes));
			if (!oldSiteRes)
			{
				PRInt32	protocolOffset = site.FindChar(':', 0);
				if (protocolOffset >= 0)
				{
					site.Cut(0, protocolOffset+1);
					while (site[0] == PRUnichar('/'))
					{
						site.Cut(0, 1);
					}
					PRInt32	slashOffset = site.FindChar('/', 0);
					if (slashOffset >= 0)
					{
						site.Truncate(slashOffset);
					}
					if (!site.IsEmpty())
					{
						const PRUnichar	*siteUni = site.get();
						if (siteUni)
						{
							nsCOMPtr<nsIRDFLiteral>	siteLiteral;
							if (NS_SUCCEEDED(rv = gRDFService->GetLiteral(siteUni, getter_AddRefs(siteLiteral))))
							{
								if (siteLiteral)
								{
									mInner->Assert(res, kNC_Site, siteLiteral, PR_TRUE);
								}
							}
						}
					}
				}
			}

			// look for name
			nsAutoString	nameStr;

			if ((!nameStartStr.IsEmpty()) && (!nameEndStr.IsEmpty()))
			{
				PRInt32		nameStart;
				if ((nameStart = nsString_Find(nameStartStr, resultItem, PR_TRUE)) >= 0)
				{
					nameStart += nameStartStr.Length();
					PRInt32	nameEnd = nsString_Find(nameEndStr, resultItem, PR_TRUE, nameStart);
					if (nameEnd > nameStart)
					{
						resultItem.Mid(nameStr, nameStart, nameEnd - nameStart);
					}
				}
			}

			if (nameStr.IsEmpty())
			{
				PRInt32	anchorEnd = resultItem.FindChar('>', quoteEndOffset);
				if (anchorEnd < quoteEndOffset)
				{
#ifdef	DEBUG_SEARCH_OUTPUT
					printf("\n\nSearch: Unable to find ending > when computing name.\n\n");
#endif
					continue;
				}
				PRInt32	anchorStop = resultItem.Find("</A>", PR_TRUE, quoteEndOffset);
				if (anchorStop < anchorEnd)
				{
#ifdef	DEBUG_SEARCH_OUTPUT
					printf("\n\nSearch: Unable to find </A> tag to compute name.\n\n");
#endif
					continue;
				}
				resultItem.Mid(nameStr, anchorEnd + 1, anchorStop - anchorEnd - 1);
			}

			ConvertEntities(nameStr);

			// look for Name (if it isn't already set)
			nsCOMPtr<nsIRDFNode>		oldNameRes = nsnull;
			if (!oldNameRes)
			{
				if (!nameStr.IsEmpty())
				{
					const PRUnichar	*nameUni = nameStr.get();
					if (nameUni)
					{
						nsCOMPtr<nsIRDFLiteral>	nameLiteral;
						if (NS_SUCCEEDED(rv = gRDFService->GetLiteral(nameUni, getter_AddRefs(nameLiteral))))
						{
							if (nameLiteral)
							{
								mInner->Assert(res, kNC_Name, nameLiteral, PR_TRUE);
							}
						}
					}
				}
			}

			// look for date
			if (!dateStartStr.IsEmpty())
			{
				nsAutoString	dateItem;
				PRInt32		dateStart;
				if ((dateStart = nsString_Find(dateStartStr, resultItem, PR_TRUE)) >= 0)
				{
					dateStart += dateStartStr.Length();
					PRInt32	dateEnd = nsString_Find(dateEndStr, resultItem, PR_TRUE, dateStart);
					if (dateEnd > dateStart)
					{
						resultItem.Mid(dateItem, dateStart, dateEnd - dateStart);
					}
				}
				
				// strip out any html tags
				PRInt32		ltOffset, gtOffset;
				while ((ltOffset = dateItem.FindChar(PRUnichar('<'))) >= 0)
				{
					if ((gtOffset = dateItem.FindChar(PRUnichar('>'), ltOffset)) <= ltOffset)
						break;
					dateItem.Cut(ltOffset, gtOffset - ltOffset + 1);
				}

				// strip out whitespace and any CR/LFs
				dateItem.Trim("\n\r\t ");

				if (!dateItem.IsEmpty())
				{
					const PRUnichar		*dateUni = dateItem.get();
					nsCOMPtr<nsIRDFLiteral>	dateLiteral;
					if (NS_SUCCEEDED(rv = gRDFService->GetLiteral(dateUni, getter_AddRefs(dateLiteral))))
					{
						if (dateLiteral)
						{
							mInner->Assert(res, kNC_Date, dateLiteral, PR_TRUE);
							hasDateFlag = PR_TRUE;
						}
					}
				}
			}

			// look for price
			if (!priceStartStr.IsEmpty())
			{
				nsAutoString	priceItem;
				PRInt32		priceStart;
				if ((priceStart = nsString_Find(priceStartStr, resultItem, PR_TRUE)) >= 0)
				{
					priceStart += priceStartStr.Length();
					PRInt32	priceEnd = nsString_Find(priceEndStr, resultItem, PR_TRUE, priceStart);
					if (priceEnd > priceStart)
					{
						resultItem.Mid(priceItem, priceStart, priceEnd - priceStart);
						ConvertEntities(priceItem);
					}
				}
				if (!priceItem.IsEmpty())
				{
					const PRUnichar		*priceUni = priceItem.get();
					nsCOMPtr<nsIRDFLiteral>	priceLiteral;
					if (NS_SUCCEEDED(rv = gRDFService->GetLiteral(priceUni, getter_AddRefs(priceLiteral))))
					{
						if (priceLiteral)
						{
							mInner->Assert(res, kNC_Price, priceLiteral, PR_TRUE);
							hasPriceFlag = PR_TRUE;
						}
					}

					PRInt32	priceCharStartOffset = priceItem.FindCharInSet("1234567890");
					if (priceCharStartOffset >= 0)
					{
						priceItem.Cut(0, priceCharStartOffset);
						PRInt32	priceErr;
						float	val = priceItem.ToFloat(&priceErr);
						if (priceItem.FindChar(PRUnichar('.')) >= 0)	val *= 100;

						nsCOMPtr<nsIRDFInt>	priceSortLiteral;
						if (NS_SUCCEEDED(rv = gRDFService->GetIntLiteral((PRInt32)val, getter_AddRefs(priceSortLiteral))))
						{
							if (priceSortLiteral)
							{
								mInner->Assert(res, kNC_PriceSort, priceSortLiteral, PR_TRUE);
							}
						}
					}
				}
				// look for availability (if we looked for price)
				if (!availStartStr.IsEmpty())
				{
					nsAutoString	availItem;
					PRInt32		availStart;
					if ((availStart = nsString_Find(availStartStr, resultItem, PR_TRUE)) >= 0)
					{
						availStart += availStartStr.Length();
						PRInt32	availEnd = nsString_Find(availEndStr, resultItem, PR_TRUE, availStart);
						if (availEnd > availStart)
						{
							resultItem.Mid(availItem, availStart, availEnd - availStart);
							ConvertEntities(availItem);
						}
					}
					if (!availItem.IsEmpty())
					{
						const PRUnichar		*availUni = availItem.get();
						nsCOMPtr<nsIRDFLiteral>	availLiteral;
						if (NS_SUCCEEDED(rv = gRDFService->GetLiteral(availUni, getter_AddRefs(availLiteral))))
						{
							if (availLiteral)
							{
								mInner->Assert(res, kNC_Availability, availLiteral, PR_TRUE);
								hasAvailabilityFlag = PR_TRUE;
							}
						}
					}
				}
			}

			// look for relevance
			nsAutoString	relItem;
			PRInt32		relStart;
			if ((relStart = nsString_Find(relevanceStartStr, resultItem, PR_TRUE)) >= 0)
			{
				relStart += relevanceStartStr.Length();
				PRInt32	relEnd = nsString_Find(relevanceEndStr, resultItem, PR_TRUE);
				if (relEnd > relStart)
				{
					resultItem.Mid(relItem, relStart, relEnd - relStart);
				}
			}

			// look for Relevance (if it isn't already set)
			nsCOMPtr<nsIRDFNode>		oldRelRes = nsnull;
			if (!oldRelRes)
			{
				if (!relItem.IsEmpty())
				{
					// save real relevance
					const PRUnichar	*relUni = relItem.get();
					if (relUni)
					{
						nsAutoString	relStr(relUni);
						// take out any characters that aren't numeric or "%"
						PRInt32	len = relStr.Length();
						for (PRInt32 x=len-1; x>=0; x--)
						{
							PRUnichar	ch;
							ch = relStr.CharAt(x);
							if ((ch != PRUnichar('%')) &&
								((ch < PRUnichar('0')) || (ch > PRUnichar('9'))))
							{
								relStr.Cut(x, 1);
							}
						}
						// make sure it ends with a "%"
						len = relStr.Length();
						if (len > 0)
						{
							PRUnichar	ch;
							ch = relStr.CharAt(len - 1);
							if (ch != PRUnichar('%'))
							{
								relStr += PRUnichar('%');
							}
							relItem = relStr;
							hasRelevanceFlag = PR_TRUE;
						}
						else
						{
							relItem.Truncate();
						}
					}
				}
				if (relItem.IsEmpty())
				{
					relItem.AssignLiteral("-");
				}

				const PRUnichar *relItemUni = relItem.get();
				nsCOMPtr<nsIRDFLiteral>	relLiteral;
				if (NS_SUCCEEDED(rv = gRDFService->GetLiteral(relItemUni, getter_AddRefs(relLiteral))))
				{
					if (relLiteral)
					{
						mInner->Assert(res, kNC_Relevance, relLiteral, PR_TRUE);
					}
				}

				if ((!relItem.IsEmpty()) && (!relItem.EqualsLiteral("-")))
				{
					// If its a percentage, remove "%"
					if (relItem[relItem.Length()-1] == PRUnichar('%'))
					{
						relItem.Cut(relItem.Length()-1, 1);
					}

					// left-pad with "0"s and set special sorting value
					nsAutoString	zero;
					zero.AssignLiteral("000");
					if (relItem.Length() < 3)
					{
						relItem.Insert(zero.get(), 0, zero.Length() - relItem.Length()); 
					}
				}
				else
				{
					relItem.AssignLiteral("000");
				}

				const PRUnichar	*relSortUni = relItem.get();
				if (relSortUni)
				{
					nsCOMPtr<nsIRDFLiteral>	relSortLiteral;
					if (NS_SUCCEEDED(rv = gRDFService->GetLiteral(relSortUni, getter_AddRefs(relSortLiteral))))
					{
						if (relSortLiteral)
						{
							mInner->Assert(res, kNC_RelevanceSort, relSortLiteral, PR_TRUE);
						}
					}
				}
			}

			// set reference to engine this came from (if it isn't already set)
			nsCOMPtr<nsIRDFNode>		oldEngineRes = nsnull;
			if (!oldEngineRes)
			{
				if (!engineStr.IsEmpty())
				{
					const PRUnichar		*engineUni = engineStr.get();
					nsCOMPtr<nsIRDFLiteral>	engineLiteral;
					if (NS_SUCCEEDED(rv = gRDFService->GetLiteral(engineUni, getter_AddRefs(engineLiteral))))
					{
						if (engineLiteral)
						{
							mInner->Assert(res, kNC_Engine, engineLiteral, PR_TRUE);
						}
					}
				}
			}

			// copy the engine's icon reference (if it has one) onto the result node
			nsCOMPtr<nsIRDFNode>		engineIconNode = nsnull;
			mInner->GetTarget(mEngine, kNC_Icon, PR_TRUE, getter_AddRefs(engineIconNode));

			// if no branding icon, use some default icons
			nsAutoString	iconChromeDefault;

			if (browserResultTypeStr.LowerCaseEqualsLiteral("category"))
				iconChromeDefault.AssignLiteral("chrome://communicator/skin/search/category.gif");
			else if ((browserResultTypeStr.LowerCaseEqualsLiteral("result")) && (!engineIconNode))
				iconChromeDefault.AssignLiteral("chrome://communicator/skin/search/result.gif");

			if (!iconChromeDefault.IsEmpty())
			{
				const PRUnichar		*iconUni = iconChromeDefault.get();
				nsCOMPtr<nsIRDFLiteral>	iconLiteral;
				if (NS_SUCCEEDED(rv = gRDFService->GetLiteral(iconUni, getter_AddRefs(iconLiteral))))
				{
					if (iconLiteral)
					{
						mInner->Assert(res, kNC_Icon, iconLiteral, PR_TRUE);
					}
				}
			}
			else if (engineIconNode)
			{
				rv = mInner->Assert(res, kNC_Icon, engineIconNode, PR_TRUE);
			}

			// set result page rank
			nsCOMPtr<nsIRDFInt>	pageRankLiteral;
			if (NS_SUCCEEDED(rv = gRDFService->GetIntLiteral(pageRank++, getter_AddRefs(pageRankLiteral))))
			{
				rv = mInner->Assert(res, kNC_PageRank, pageRankLiteral, PR_TRUE);
			}

			// set the result type
			if (!browserResultTypeStr.IsEmpty())
			{
				const PRUnichar		*resultTypeUni = browserResultTypeStr.get();
				nsCOMPtr<nsIRDFLiteral>	resultTypeLiteral;
				if (NS_SUCCEEDED(rv = gRDFService->GetLiteral(resultTypeUni, getter_AddRefs(resultTypeLiteral))))
				{
					if (resultTypeLiteral)
					{
						mInner->Assert(res, kNC_SearchType, resultTypeLiteral, PR_TRUE);
					}
				}
			}

			// set the node type
			rv = mInner->Assert(res, kRDF_type, kNC_SearchResult, PR_TRUE);

			// Note: always add in parent-child relationship last!  (if it isn't already set)
#ifdef	OLDWAY
			PRBool		parentHasChildFlag = PR_FALSE;
			if (mParent)
			{
				mInner->HasAssertion(mParent, kNC_Child, res, PR_TRUE, &parentHasChildFlag);
			}
      if (!parentHasChildFlag)
#endif
			{
				if (mParent)
				{
					rv = mInner->Assert(mParent, kNC_Child, res, PR_TRUE);
				}
			}

			// Persist this under kNC_LastSearchRoot
			if (mInner)
			{
				rv = mInner->Assert(kNC_LastSearchRoot, kNC_Child, res, PR_TRUE);
			}

			++numResults;

			// place a cap on the # of results we can process
			if (numResults >= MAX_SEARCH_RESULTS_ALLOWED)
				break;

		}
	}

	// set hints so that the appropriate columns can be displayed
	if (mParent)
	{
    if (hasPriceFlag)
      SetHint(mParent, kNC_Price);
    if (hasAvailabilityFlag)
      SetHint(mParent, kNC_Availability);
    if (hasRelevanceFlag)
      SetHint(mParent, kNC_Relevance);
    if (hasDateFlag)
      SetHint(mParent, kNC_Date);
	}

#ifdef	DEBUG
	PRTime		now2;
	now2 = PR_Now();
	PRUint64	loadTime64;
	LL_SUB(loadTime64, now2, now);
	PRUint32	loadTime32;
	LL_L2UI(loadTime32, loadTime64);
	printf("Finished processing search results  (%u microseconds)\n", loadTime32);
#endif

	return(NS_OK);
}



nsresult
InternetSearchDataSource::SetHint(nsIRDFResource *mParent, nsIRDFResource *hintRes)
{
	if (!mInner)	return(NS_OK);

	nsresult	rv;
	PRBool		hasAssertionFlag = PR_FALSE;
  rv = mInner->HasAssertion(mParent, hintRes, kTrueLiteral, PR_TRUE,
                            &hasAssertionFlag);
  if (NS_SUCCEEDED(rv) && !hasAssertionFlag)
	{
		rv = mInner->Assert(mParent, hintRes, kTrueLiteral, PR_TRUE);
	}
	return(rv);
}



nsresult
InternetSearchDataSource::ConvertEntities(nsString &nameStr, PRBool removeHTMLFlag,
					PRBool removeCRLFsFlag, PRBool trimWhiteSpaceFlag)
{
	PRInt32	startOffset = 0, ampOffset, semiOffset, offset;

	// do this before converting entities
  if (removeHTMLFlag)
	{
		// munge out anything inside of HTML "<" / ">" tags
		while ((offset = nameStr.FindChar(PRUnichar('<'), 0)) >= 0)
		{
			PRInt32	offsetEnd = nameStr.FindChar(PRUnichar('>'), offset+1);
			if (offsetEnd <= offset)	break;
			nameStr.Cut(offset, offsetEnd - offset + 1);
		}
	}
	while ((ampOffset = nameStr.FindChar(PRUnichar('&'), startOffset)) >= 0)
	{
		if ((semiOffset = nameStr.FindChar(PRUnichar(';'), ampOffset+1)) <= ampOffset)
			break;

		nsAutoString	entityStr;
		nameStr.Mid(entityStr, ampOffset, semiOffset-ampOffset+1);
		nameStr.Cut(ampOffset, semiOffset-ampOffset+1);

		PRUnichar	entityChar = 0;
		if (entityStr.LowerCaseEqualsLiteral("&quot;"))	entityChar = PRUnichar('\"');
		else if (entityStr.LowerCaseEqualsLiteral("&amp;"))	entityChar = PRUnichar('&');
		else if (entityStr.LowerCaseEqualsLiteral("&nbsp;"))	entityChar = PRUnichar(' ');
		else if (entityStr.LowerCaseEqualsLiteral("&lt;"))		entityChar = PRUnichar('<');
		else if (entityStr.LowerCaseEqualsLiteral("&gt;"))		entityChar = PRUnichar('>');
		else if (entityStr.LowerCaseEqualsLiteral("&iexcl;"))	entityChar = PRUnichar(161);
		else if (entityStr.LowerCaseEqualsLiteral("&cent;"))	entityChar = PRUnichar(162);
		else if (entityStr.LowerCaseEqualsLiteral("&pound;"))	entityChar = PRUnichar(163);
		else if (entityStr.LowerCaseEqualsLiteral("&curren;"))	entityChar = PRUnichar(164);
		else if (entityStr.LowerCaseEqualsLiteral("&yen;"))	entityChar = PRUnichar(165);
		else if (entityStr.LowerCaseEqualsLiteral("&brvbar;"))	entityChar = PRUnichar(166);
		else if (entityStr.LowerCaseEqualsLiteral("&sect;"))	entityChar = PRUnichar(167);
		else if (entityStr.LowerCaseEqualsLiteral("&uml;"))	entityChar = PRUnichar(168);
		else if (entityStr.LowerCaseEqualsLiteral("&copy;"))	entityChar = PRUnichar(169);
		else if (entityStr.LowerCaseEqualsLiteral("&ordf;"))	entityChar = PRUnichar(170);
		else if (entityStr.LowerCaseEqualsLiteral("&laquo;"))	entityChar = PRUnichar(171);
		else if (entityStr.LowerCaseEqualsLiteral("&not;"))	entityChar = PRUnichar(172);
		else if (entityStr.LowerCaseEqualsLiteral("&shy;"))	entityChar = PRUnichar(173);
		else if (entityStr.LowerCaseEqualsLiteral("&reg;"))	entityChar = PRUnichar(174);
		else if (entityStr.LowerCaseEqualsLiteral("&macr;"))	entityChar = PRUnichar(175);
		else if (entityStr.LowerCaseEqualsLiteral("&deg;"))	entityChar = PRUnichar(176);
		else if (entityStr.LowerCaseEqualsLiteral("&plusmn;"))	entityChar = PRUnichar(177);
		else if (entityStr.LowerCaseEqualsLiteral("&sup2;"))	entityChar = PRUnichar(178);
		else if (entityStr.LowerCaseEqualsLiteral("&sup3;"))	entityChar = PRUnichar(179);
		else if (entityStr.LowerCaseEqualsLiteral("&acute;"))	entityChar = PRUnichar(180);
		else if (entityStr.LowerCaseEqualsLiteral("&micro;"))	entityChar = PRUnichar(181);
		else if (entityStr.LowerCaseEqualsLiteral("&para;"))	entityChar = PRUnichar(182);
		else if (entityStr.LowerCaseEqualsLiteral("&middot;"))	entityChar = PRUnichar(183);
		else if (entityStr.LowerCaseEqualsLiteral("&cedil;"))	entityChar = PRUnichar(184);
		else if (entityStr.LowerCaseEqualsLiteral("&sup1;"))	entityChar = PRUnichar(185);
		else if (entityStr.LowerCaseEqualsLiteral("&ordm;"))	entityChar = PRUnichar(186);
		else if (entityStr.LowerCaseEqualsLiteral("&raquo;"))	entityChar = PRUnichar(187);
		else if (entityStr.LowerCaseEqualsLiteral("&frac14;"))	entityChar = PRUnichar(188);
		else if (entityStr.LowerCaseEqualsLiteral("&frac12;"))	entityChar = PRUnichar(189);
		else if (entityStr.LowerCaseEqualsLiteral("&frac34;"))	entityChar = PRUnichar(190);
		else if (entityStr.LowerCaseEqualsLiteral("&iquest;"))	entityChar = PRUnichar(191);
		else if (entityStr.LowerCaseEqualsLiteral("&agrave;"))	entityChar = PRUnichar(192);
		else if (entityStr.LowerCaseEqualsLiteral("&aacute;"))	entityChar = PRUnichar(193);
		else if (entityStr.LowerCaseEqualsLiteral("&acirc;"))	entityChar = PRUnichar(194);
		else if (entityStr.LowerCaseEqualsLiteral("&atilde;"))	entityChar = PRUnichar(195);
		else if (entityStr.LowerCaseEqualsLiteral("&auml;"))	entityChar = PRUnichar(196);
		else if (entityStr.LowerCaseEqualsLiteral("&aring;"))	entityChar = PRUnichar(197);
		else if (entityStr.LowerCaseEqualsLiteral("&aelig;"))	entityChar = PRUnichar(198);
		else if (entityStr.LowerCaseEqualsLiteral("&ccedil;"))	entityChar = PRUnichar(199);
		else if (entityStr.LowerCaseEqualsLiteral("&egrave;"))	entityChar = PRUnichar(200);
		else if (entityStr.LowerCaseEqualsLiteral("&eacute;"))	entityChar = PRUnichar(201);
		else if (entityStr.LowerCaseEqualsLiteral("&ecirc;"))	entityChar = PRUnichar(202);
		else if (entityStr.LowerCaseEqualsLiteral("&euml;"))	entityChar = PRUnichar(203);
		else if (entityStr.LowerCaseEqualsLiteral("&igrave;"))	entityChar = PRUnichar(204);
		else if (entityStr.LowerCaseEqualsLiteral("&iacute;"))	entityChar = PRUnichar(205);
		else if (entityStr.LowerCaseEqualsLiteral("&icirc;"))	entityChar = PRUnichar(206);
		else if (entityStr.LowerCaseEqualsLiteral("&iuml;"))	entityChar = PRUnichar(207);
		else if (entityStr.LowerCaseEqualsLiteral("&eth;"))	entityChar = PRUnichar(208);
		else if (entityStr.LowerCaseEqualsLiteral("&ntilde;"))	entityChar = PRUnichar(209);
		else if (entityStr.LowerCaseEqualsLiteral("&ograve;"))	entityChar = PRUnichar(210);
		else if (entityStr.LowerCaseEqualsLiteral("&oacute;"))	entityChar = PRUnichar(211);
		else if (entityStr.LowerCaseEqualsLiteral("&ocirc;"))	entityChar = PRUnichar(212);
		else if (entityStr.LowerCaseEqualsLiteral("&otilde;"))	entityChar = PRUnichar(213);
		else if (entityStr.LowerCaseEqualsLiteral("&ouml;"))	entityChar = PRUnichar(214);
		else if (entityStr.LowerCaseEqualsLiteral("&times;"))	entityChar = PRUnichar(215);
		else if (entityStr.LowerCaseEqualsLiteral("&oslash;"))	entityChar = PRUnichar(216);
		else if (entityStr.LowerCaseEqualsLiteral("&ugrave;"))	entityChar = PRUnichar(217);
		else if (entityStr.LowerCaseEqualsLiteral("&uacute;"))	entityChar = PRUnichar(218);
		else if (entityStr.LowerCaseEqualsLiteral("&ucirc;"))	entityChar = PRUnichar(219);
		else if (entityStr.LowerCaseEqualsLiteral("&uuml;"))	entityChar = PRUnichar(220);
		else if (entityStr.LowerCaseEqualsLiteral("&yacute;"))	entityChar = PRUnichar(221);
		else if (entityStr.LowerCaseEqualsLiteral("&thorn;"))	entityChar = PRUnichar(222);
		else if (entityStr.LowerCaseEqualsLiteral("&szlig;"))	entityChar = PRUnichar(223);
		else if (entityStr.LowerCaseEqualsLiteral("&agrave;"))	entityChar = PRUnichar(224);
		else if (entityStr.LowerCaseEqualsLiteral("&aacute;"))	entityChar = PRUnichar(225);
		else if (entityStr.LowerCaseEqualsLiteral("&acirc;"))	entityChar = PRUnichar(226);
		else if (entityStr.LowerCaseEqualsLiteral("&atilde;"))	entityChar = PRUnichar(227);
		else if (entityStr.LowerCaseEqualsLiteral("&auml;"))	entityChar = PRUnichar(228);
		else if (entityStr.LowerCaseEqualsLiteral("&aring;"))	entityChar = PRUnichar(229);
		else if (entityStr.LowerCaseEqualsLiteral("&aelig;"))	entityChar = PRUnichar(230);
		else if (entityStr.LowerCaseEqualsLiteral("&ccedil;"))	entityChar = PRUnichar(231);
		else if (entityStr.LowerCaseEqualsLiteral("&egrave;"))	entityChar = PRUnichar(232);
		else if (entityStr.LowerCaseEqualsLiteral("&eacute;"))	entityChar = PRUnichar(233);
		else if (entityStr.LowerCaseEqualsLiteral("&ecirc;"))	entityChar = PRUnichar(234);
		else if (entityStr.LowerCaseEqualsLiteral("&euml;"))	entityChar = PRUnichar(235);
		else if (entityStr.LowerCaseEqualsLiteral("&igrave;"))	entityChar = PRUnichar(236);
		else if (entityStr.LowerCaseEqualsLiteral("&iacute;"))	entityChar = PRUnichar(237);
		else if (entityStr.LowerCaseEqualsLiteral("&icirc;"))	entityChar = PRUnichar(238);
		else if (entityStr.LowerCaseEqualsLiteral("&iuml;"))	entityChar = PRUnichar(239);
		else if (entityStr.LowerCaseEqualsLiteral("&eth;"))	entityChar = PRUnichar(240);
		else if (entityStr.LowerCaseEqualsLiteral("&ntilde;"))	entityChar = PRUnichar(241);
		else if (entityStr.LowerCaseEqualsLiteral("&ograve;"))	entityChar = PRUnichar(242);
		else if (entityStr.LowerCaseEqualsLiteral("&oacute;"))	entityChar = PRUnichar(243);
		else if (entityStr.LowerCaseEqualsLiteral("&ocirc;"))	entityChar = PRUnichar(244);
		else if (entityStr.LowerCaseEqualsLiteral("&otilde;"))	entityChar = PRUnichar(245);
		else if (entityStr.LowerCaseEqualsLiteral("&ouml;"))	entityChar = PRUnichar(246);
		else if (entityStr.LowerCaseEqualsLiteral("&divide;"))	entityChar = PRUnichar(247);
		else if (entityStr.LowerCaseEqualsLiteral("&oslash;"))	entityChar = PRUnichar(248);
		else if (entityStr.LowerCaseEqualsLiteral("&ugrave;"))	entityChar = PRUnichar(249);
		else if (entityStr.LowerCaseEqualsLiteral("&uacute;"))	entityChar = PRUnichar(250);
		else if (entityStr.LowerCaseEqualsLiteral("&ucirc;"))	entityChar = PRUnichar(251);
		else if (entityStr.LowerCaseEqualsLiteral("&uuml;"))	entityChar = PRUnichar(252);
		else if (entityStr.LowerCaseEqualsLiteral("&yacute;"))	entityChar = PRUnichar(253);
		else if (entityStr.LowerCaseEqualsLiteral("&thorn;"))	entityChar = PRUnichar(254);
		else if (entityStr.LowerCaseEqualsLiteral("&yuml;"))	entityChar = PRUnichar(255);

		startOffset = ampOffset;
		if (entityChar != 0)
		{
			nameStr.Insert(entityChar, ampOffset);
			++startOffset;
		}
	}

  if (removeCRLFsFlag)
	{
		// cut out any CRs or LFs
		while ((offset = nameStr.FindCharInSet("\n\r", 0)) >= 0)
		{
			nameStr.Cut(offset, 1);
		}
	}

  if (trimWhiteSpaceFlag)
	{
		// trim name
		nameStr.Trim(" \t");
	}

	return(NS_OK);
}

NS_IMETHODIMP
InternetSearchDataSource::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *someData)
{
    nsresult rv = NS_OK;

    if (!nsCRT::strcmp(aTopic, "profile-before-change"))
    {
        // The profile is about to change.
        categoryDataSource = nsnull;

        if (!nsCRT::strcmp(someData, NS_LITERAL_STRING("shutdown-cleanse").get()))
        {
            // Delete search.rdf
            nsCOMPtr<nsIFile> searchFile;
            rv = NS_GetSpecialDirectory(NS_APP_SEARCH_50_FILE, getter_AddRefs(searchFile));
            if (NS_SUCCEEDED(rv))
                rv = searchFile->Remove(PR_FALSE);
        }
    }
    else if (!nsCRT::strcmp(aTopic, "profile-do-change"))
    {
        // The profile has aleady changed.
        if (!categoryDataSource)
          GetCategoryList();
    }

    return rv;
}

NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(LocalSearchDataSource, Init)
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(InternetSearchDataSource, Init)

static const nsModuleComponentInfo components[] = {
    { "Local Search", NS_RDFFINDDATASOURCE_CID,
      NS_LOCALSEARCH_SERVICE_CONTRACTID, LocalSearchDataSourceConstructor },
    { "Local Search", NS_RDFFINDDATASOURCE_CID,
      NS_LOCALSEARCH_DATASOURCE_CONTRACTID, LocalSearchDataSourceConstructor },
    { "Internet Search", NS_RDFSEARCHDATASOURCE_CID,
      NS_INTERNETSEARCH_SERVICE_CONTRACTID, InternetSearchDataSourceConstructor },
    { "Internet Search", NS_RDFSEARCHDATASOURCE_CID,
      NS_INTERNETSEARCH_DATASOURCE_CONTRACTID, InternetSearchDataSourceConstructor },
};

NS_IMPL_NSGETMODULE(SearchServiceModule, components)
