/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

/* implements disk caching:  See mkmemcac.c for memory cache
 *
 * Designed and originally implemented by Lou Montulli '94
 * Modifications/additions by Gagan Saksena '97
 */

/* Please leave outside of ifdef for windows precompiled headers */
#include "mkutils.h"

#ifdef MOZILLA_CLIENT

/* dbm code */

#include "extcache.h"
#include "mkcache.h"
#include "glhist.h"
#include "xp_hash.h"
#include "xp_mcom.h"
#include "client.h"
#include "mkgeturl.h"
#include "mkstream.h"
#include "cert.h" /* for CERT_DupCertificate() */
#include "mcom_db.h"
#include "prclist.h"

#include "mkextcac.h"
#include "mkfile.h"
#include "mkmemcac.h"
#include "merrors.h"

#ifdef PROFILE
#pragma profile on
#endif

#ifdef XP_OS2_NOT_YET
/*serious hack to put cache cleanup on another thread...*/
extern int XP_LazyFileRemove(const char * name, XP_FileType type);
#else
#define XP_LazyFileRemove  XP_FileRemove
#endif

/* for XP_GetString() */
#include "xpgetstr.h"
extern int XP_CACHE_CLEANUP;
extern int MK_CACHE_CONTENT_LENGTH;
extern int MK_CACHE_REAL_CONTENT_LENGTH;
extern int MK_CACHE_CONTENT_TYPE;
extern int MK_CACHE_LOCAL_FILENAME;
extern int MK_CACHE_LAST_MODIFIED;
extern int MK_CACHE_EXPIRES;
extern int MK_CACHE_LAST_ACCESSED;
extern int MK_CACHE_CHARSET;
extern int MK_CACHE_SECURE;
extern int MK_CACHE_USES_RELATIVE_PATH;
extern int MK_CACHE_FROM_NETSITE_SERVER;

#define INTERNAL_NAME_PREFIX       "INT_"
#define DISK_CACHE_SIZE_KEY_NAME   "INT_DiskCacheSize"
#define DISK_CACHE_NUMBER_KEY_NAME "INT_DiskCacheNumber"
#define DISK_CACHE_NAME			   "Netscape Internal Disk Cache"

#define CACHE_SYNC_RATE 30

typedef enum {
	NEW_CACHE_STORE,
	REFRESH_CACHE_STORE
} store_type_enum;

PRIVATE DBT       *net_DiskCacheSizeKey=0;
PRIVATE DBT		  *net_DiskCacheNumberKey=0;
PRIVATE DBT		  *net_DiskCacheNameKey=0;
PRIVATE uint32     net_DiskCacheSize=0;
PRIVATE uint32     net_NumberInDiskCache=0;
PRIVATE uint32     net_MaxDiskCacheSize=0;
PRIVATE uint32     net_cache_file_block_size=0;

/* Saves the cache struct to the DB */
extern void CACHE_SaveCacheInfoToDB(ExtCacheDBInfo *db_info);

#ifdef NO_MEMORY_CACHE
PRIVATE uint32     net_MaxMemoryCacheSize=0;
#endif /* NO_MEMORY_CACHE */

/* trace variable for cache testing */
MODULE_PRIVATE XP_Bool NET_CacheTraceOn = FALSE;

PRIVATE XP_Bool net_dont_disk_cache_ssl = FALSE;
PRIVATE DB * cache_database = 0; 
/* PRIVATE XXX Mac CodeWarrior bug */ PRCList active_cache_data_objects
	= PR_INIT_STATIC_CLIST(&active_cache_data_objects);

typedef struct _CacheDataObject {
	PRCList          links;
    XP_File          fp;
    NET_StreamClass *next_stream;
	URL_Struct      *URL_s;
    net_CacheObject *cache_object;
} CacheDataObject;

PRIVATE void net_RemoveAllDiskCacheObjects(void);

/* pass in TRUE to disable disk caching
 * of SSL documents.
 * pass in FALSE to enable disk cacheing
 * of SSL documents
 */
PUBLIC void
NET_DontDiskCacheSSL(XP_Bool set)
{
	net_dont_disk_cache_ssl = set;
}

/* return the size of the file on
 * disk based on the block size
 */
PRIVATE
uint32
net_calc_real_file_size(uint32 size)
{
	if(net_cache_file_block_size == 0)
		return(size);
	else  /* round sizes up to the next block size */
		return(PR_ROUNDUP(size, net_cache_file_block_size));
}

PRIVATE void
net_GetDiskCacheSize(void)
{
    DBT data;

    if(!cache_database)
        return;

    if(!net_DiskCacheSizeKey)
      {
        net_DiskCacheSizeKey = XP_NEW(DBT);
		if(!net_DiskCacheSizeKey)
			return;

        net_DiskCacheSizeKey->data = 0;
        BlockAllocCopy(net_DiskCacheSizeKey->data, 
		       		   DISK_CACHE_SIZE_KEY_NAME, 
					   XP_STRLEN(DISK_CACHE_SIZE_KEY_NAME));
        net_DiskCacheSizeKey->size = XP_STRLEN(DISK_CACHE_SIZE_KEY_NAME);
      }

    if(0 == (*cache_database->get)(cache_database, 
								   net_DiskCacheSizeKey, 
								   &data, 
								   0))
	  {
		if(data.size == sizeof(uint32))
		  {
			COPY_INT32(&net_DiskCacheSize, data.data);
		  }
		else
		  {
			net_DiskCacheSize = 0;
		  }
	  }
	else
	  {
		net_DiskCacheSize = 0;
	  }

    if(!net_DiskCacheNumberKey)
      {
        net_DiskCacheNumberKey = XP_NEW(DBT);
		if(!net_DiskCacheNumberKey)
			return;

        net_DiskCacheNumberKey->data = 0;
        BlockAllocCopy(net_DiskCacheNumberKey->data, 
					   DISK_CACHE_NUMBER_KEY_NAME,
					   XP_STRLEN(DISK_CACHE_NUMBER_KEY_NAME));
        net_DiskCacheNumberKey->size = XP_STRLEN(DISK_CACHE_NUMBER_KEY_NAME);
      }

    if(0 == (*cache_database->get)(cache_database,
                                   net_DiskCacheNumberKey,
                                   &data,
                                   0))
      {
        if(data.size == sizeof(uint32))
		  {
            COPY_INT32(&net_NumberInDiskCache, data.data);
		  }
		else
		  {
			net_NumberInDiskCache = 0;
		  }
      }
	else
	  {
		net_NumberInDiskCache = 0;
	  }

}


PRIVATE int
net_OpenCacheFatDB(void)
{
	char* filename;
	static Bool have_tried_open=FALSE;

    if(!cache_database)
      {
	HASHINFO hash_info = {
		4 * 1024,
		0,
		0,
#ifdef WIN16
		60 * 1024,
#else
		96 * 1024,
#endif
		0,
		0};
	    filename = WH_FileName("", xpCacheFAT);
		if (!filename) return 0;

		/* @@@ add .db to the end of the files
		 */
		cache_database = dbopen(filename,
								O_RDWR | O_CREAT,
								0600,
								DB_HASH,
								&hash_info);
		XP_FREE(filename);

        if(!have_tried_open && !cache_database)
          {
    		XP_StatStruct stat_entry;

			have_tried_open = TRUE; /* only do this once */

            TRACEMSG(("Could not open cache database -- errno: %d", errno));

			/* if the file is zero length remove it
			 */
         	if(XP_Stat("", &stat_entry, xpCacheFAT) != -1)
			  {
           		if(stat_entry.st_size <= 0)
				  {
					XP_FileRemove("", xpCacheFAT);
				  }
                else
                  {
                    XP_File fp;
#define BUFF_SIZE 1024
                    char buffer[BUFF_SIZE];

                    /* open the file and look for
                     * the old magic cookie.  If it's
                     * there delete the file
                     */
                    fp = XP_FileOpen("", xpCacheFAT, XP_FILE_READ);

                    if(fp)
                      {
                        XP_FileReadLine(buffer, BUFF_SIZE, fp);

                        XP_FileClose(fp);

                        if(XP_STRSTR(buffer, 
								     "Cache-file-allocation-table-format"))
                          XP_FileRemove("", xpCacheFAT);

                      }
				  }
			  }


			/* try it again */
			filename = WH_FileName("", xpCacheFAT);
			if (filename) {
				cache_database = dbopen(filename,
										O_RDWR | O_CREAT,
										0600,
										DB_HASH,
										0);
				XP_FREE(filename);
			}
			else 
				cache_database = NULL;
          }

		if(cache_database)
		  {
    		if(-1 == (*cache_database->sync)(cache_database, 0))
      		  {
        		TRACEMSG(("Error syncing cache database"));

        		/* close the database */
        		(*cache_database->close)(cache_database);
        		cache_database = 0;
      		  }

			net_GetDiskCacheSize();
		  }

#if !defined(XP_WIN) && !defined(XP_OS2)
        /* if we don't know the standard block size yet
         * get it now
         */
        if(!net_cache_file_block_size)
		  {
            XP_StatStruct stat_entry;

            if(XP_Stat("", &stat_entry, xpCacheFAT) != -1)
            	net_cache_file_block_size = stat_entry.st_blksize;
		  }
#endif
      }

	/* return non-zero if the cache_database pointer is
	 * non-zero
	 */
	return((int) cache_database);

}

#if defined(DEBUG) && defined(UNIX)
int
cache_test_me()
{

	net_CacheObject test;
	net_CacheObject *rv;
	int32 total_size;
	DBT *db_obj;

	XP_MEMSET(&test, 0, sizeof(net_CacheObject));
	StrAllocCopy(test.address, "test1");
	db_obj = net_CacheStructToDBData(&test);
	rv = net_DBDataToCacheStruct(db_obj);
	printf("test1: %s\n", rv->address);

	XP_MEMSET(&test, 0, sizeof(net_CacheObject));
	StrAllocCopy(test.address, "test2");
	StrAllocCopy(test.charset, "test2");
	db_obj = net_CacheStructToDBData(&test);
	rv = net_DBDataToCacheStruct(db_obj);
	printf("test2: %s	%s\n", rv->address, rv->charset);

	XP_MEMSET(&test, 0, sizeof(net_CacheObject));
	StrAllocCopy(test.address, "test3");
	StrAllocCopy(test.charset, "test3");
	StrAllocCopy(test.key_cipher, "test3");
	test.content_length = 3 ;
	test.method = 3 ;
	test.is_netsite = 3 ;
	db_obj = net_CacheStructToDBData(&test);
	rv = net_DBDataToCacheStruct(db_obj);
	printf("test3: %s	%s	%s	%d	%d	%s\n", 
			rv->address, rv->charset, rv->key_cipher,
			rv->content_length, rv->method, 
			(rv->is_netsite == 3 ? "TRUE" : "FALSE"));

}
#endif

PRIVATE void
net_StoreDiskCacheSize(void)
{
	DBT data;
	uint32 tmp_num;

	if(!cache_database)
		return;
	
	if(!net_DiskCacheSizeKey)
	  {
		net_DiskCacheSizeKey = XP_NEW(DBT);
		if(!net_DiskCacheSizeKey)
			return;

		net_DiskCacheSizeKey->data = 0;
		BlockAllocCopy(net_DiskCacheSizeKey->data, 
					   DISK_CACHE_SIZE_KEY_NAME,
					   XP_STRLEN((char *)DISK_CACHE_SIZE_KEY_NAME));
		net_DiskCacheSizeKey->size = XP_STRLEN(DISK_CACHE_SIZE_KEY_NAME);
	  }

	if(!net_DiskCacheNumberKey)
	  {
		net_DiskCacheNumberKey = XP_NEW(DBT);
		if(!net_DiskCacheNumberKey)
			return;

		net_DiskCacheNumberKey->data = 0;
		BlockAllocCopy(net_DiskCacheNumberKey->data, 
					   DISK_CACHE_NUMBER_KEY_NAME,
					   XP_STRLEN((char *)DISK_CACHE_NUMBER_KEY_NAME));
		net_DiskCacheNumberKey->size = XP_STRLEN(DISK_CACHE_NUMBER_KEY_NAME);
	  }

	if(!net_DiskCacheNameKey)
	  {
		/* set the name of the cache so that
		 * if it is read as an external cache
		 * we will know what created it
		 */
		net_DiskCacheNameKey = XP_NEW(DBT);
		if(!net_DiskCacheNameKey)
			return;

		net_DiskCacheNameKey->data = 0;
		BlockAllocCopy(net_DiskCacheNameKey->data, 
					   EXT_CACHE_NAME_STRING,
					   XP_STRLEN((char *)EXT_CACHE_NAME_STRING));
		net_DiskCacheNameKey->size = XP_STRLEN(EXT_CACHE_NAME_STRING);
	  }

	data.size = sizeof(uint32);
	COPY_INT32(&tmp_num, &net_DiskCacheSize);
	data.data = &tmp_num;
	
	(*cache_database->put)(cache_database, net_DiskCacheSizeKey, &data, 0);

	data.size = sizeof(uint32);
	COPY_INT32(&tmp_num, &net_NumberInDiskCache);
	data.data = &tmp_num;
	
	(*cache_database->put)(cache_database, net_DiskCacheNumberKey, &data, 0);

	data.size = XP_STRLEN(DISK_CACHE_NAME)+1;
	data.data = DISK_CACHE_NAME;
	
	(*cache_database->put)(cache_database, net_DiskCacheNameKey, &data, 0);

}

/* returns TRUE if the object gets stored
 * FALSE if not
 */
PRIVATE Bool
net_CacheStore(net_CacheObject * obj,  
			   URL_Struct      * URL_s, 
			   Bool		         accept_partial_files,
			   store_type_enum 	 store_type)
{
	DBT *data, *key;
	int status;
	static cache_sync_count = 0;
    XP_StatStruct stat_entry;

	/* larubbio */
	Bool		SARCache = FALSE;
	XP_FileType fileType;
	DB			*local_cache_database = NULL;

	/* larubbio fix to allow 197 SAR cache adds without modifying 
	 * the navigator cache (too much)
	 */
	if ( URL_s != NULL && URL_s->SARCache != NULL )
	{
		SARCache = TRUE;
		fileType = xpSARCache;
		local_cache_database = URL_s->SARCache->database;
	}
	else
	{
		fileType = xpCache;
		local_cache_database = cache_database;
	}

	if(!local_cache_database)
	  {
		/* delete the file since we wont remember it
		 */
#ifdef XP_OS2
		XP_LazyFileRemove(obj->filename, fileType);
#else
		XP_FileRemove(obj->filename, fileType);
#endif
		
		return FALSE;
	  }

	if(URL_s 
		&& URL_s->server_date 
		&& URL_s->server_date < obj->last_modified)
	  {
		/* the last modified date is after the current date
		 * as given by the server.  We shoudn't cache this
		 * object
		 */

		TRACEMSG(("Found file dated into the future.  "
				"Zeroing the last modified date"));
		obj->last_modified = 0;
		URL_s->last_modified = 0;
	  }

	if(store_type == NEW_CACHE_STORE)
	  {
	    if(URL_s && URL_s->dont_cache)  
  	      {
		    /* delete the file since we wont remember it
		     */
#ifdef XP_OS2
		    XP_LazyFileRemove(obj->filename, fileType);
#else
		    XP_FileRemove(obj->filename, fileType);
#endif
		    return FALSE;
          }
        else if(XP_Stat(obj->filename, &stat_entry,  fileType) != -1)
          {
		    if(stat_entry.st_size == 0)
		      {
			    /* don't cache any zero length files
		 	     */
#ifdef XP_OS2
				XP_LazyFileRemove(obj->filename, fileType);
#else
			    XP_FileRemove(obj->filename, fileType);
#endif
			    return FALSE;
		      }

			if(obj->content_length <= 0)
			  {
		        /* fill this in for consistancy */
      		    obj->real_content_length = stat_entry.st_size;
      		    obj->content_length = stat_entry.st_size;
			  }
		    else if(accept_partial_files
			        && obj->content_length > (uint32) stat_entry.st_size)
		      {
      	        obj->real_content_length = obj->content_length;
			    obj->content_length = (uint32) stat_entry.st_size;
			    obj->incomplete_file = TRUE;
		      }
   	        else
	          {
	            /* stat the file to verify the content_length 
	             * if the content_length is positive and does not
	             * match the size of the cache file, handle it somehow
	             */
		        if(obj->content_length != (uint32) stat_entry.st_size)
 	              {
			        /* this is a case of a server or network
				     * error.  Assume that the file is corrupted,
				     * Display it as it was for history navigation
				     * but do a complete reload any time the server
				     * is contacted
				     * kill the last modified date to disable IMS 
				     */
      		        obj->last_modified = 0;
					if(URL_s)
						URL_s->last_modified = 0;
			        TRACEMSG(("\n\n!!!!!!!! Content length of cache file not equal"
					      "          To the content transfer length: %d - %d  !!!!!!!\n",
					      obj->content_length, stat_entry.st_size));
		  	      }
    
		        /* fill this in for consistancy */
      	    	
      	        obj->real_content_length 
							= obj->content_length 
										= stat_entry.st_size;
		      }

			/* must be set now */
			XP_ASSERT(obj->content_length);
			
	      }
	    else		
	      {
		    /* delete the file since we wont remember it
		     */
#ifdef XP_OS2
			XP_LazyFileRemove(obj->filename, fileType);
#else
		    XP_FileRemove(obj->filename, fileType);
#endif
		    return FALSE;
	      }

	    /* refresh these entries in case meta changes them */
	    if(URL_s)
	      {
		    obj->expires = URL_s->expires;
		    obj->last_modified = URL_s->last_modified;
		    StrAllocCopy(obj->charset, URL_s->charset);
	      }
	  }
	else if(store_type == REFRESH_CACHE_STORE)
	  {
		/* OK */
	  }
	else
	  {
		XP_ASSERT(0);
	  }

	/* update the last accessed time
	 */
	obj->last_accessed = time(NULL);  /* current time */

	/* these must be true or else the partial cache logic is screwed */
	XP_ASSERT(obj->content_length == obj->real_content_length
	          || obj->incomplete_file);

	/* create key */
	key = net_GenCacheDBKey(obj->address, obj->post_data, obj->post_data_size);

	/* create new dbt data object */
	data = net_CacheStructToDBData(obj);

	if(!key || !data)
	  {
		TRACEMSG(("Failed to get key or data: malloc error probably"));
		/* delete the file since we wont remember it
		 */
#ifdef XP_OS2
		XP_LazyFileRemove(obj->filename, fileType);
#else
		XP_FileRemove(obj->filename, fileType);
#endif
		return FALSE;
	  }

	if(store_type == NEW_CACHE_STORE)
		status = (*local_cache_database->put)(local_cache_database, 
										key, 
										data, 
										R_NOOVERWRITE);
	else /* overwrite by default */
		status = (*local_cache_database->put)(local_cache_database, key, data, 0);

    if(status > 0)
	  {
		DBT old_data;

		TRACEMSG(("Duplicate cache object found"));

		/* get the duplicate out of the database */
		status = (*local_cache_database->get)(local_cache_database, key, &old_data, 0);

		/* we should always get a success here 
		 * or at least a DB error.
		 */
		assert(status < 1);
		
		if(status == 0)
		  {
			/* found it */

		   	/* delete the previous object from the database
		     */
		   	/* remove the old file */
        	char * filename = net_GetFilenameInCacheDBT((DBT *)&old_data);
        	if(filename)
              {
#ifdef XP_OS2
				XP_LazyFileRemove(filename, fileType);
#else
            	XP_FileRemove(filename, fileType);
#endif
            	FREE(filename);
           	  }

			/* larubbio file size checks for SARCache */
			if ( SARCache )
			{
				/* subtract the size of the file being removed */
        		URL_s->SARCache->DiskCacheSize -= net_calc_real_file_size(
												net_GetInt32InCacheDBT(&old_data,
													CONTENT_LENGTH_BYTE_POSITION));
				URL_s->SARCache->NumberInDiskCache--;
			}
			else
			{
				/* subtract the size of the file being removed */
        		net_DiskCacheSize -= net_calc_real_file_size(
												net_GetInt32InCacheDBT(&old_data,
													CONTENT_LENGTH_BYTE_POSITION));
				net_NumberInDiskCache--;
			}
    
		    /* put the new data in over the old data */
		    status = (*local_cache_database->put)(local_cache_database, key, data, 0);
   	      }
	  }


	if(status < 0)
	  {
        char * filename = net_GetFilenameInCacheDBT((DBT *)&data);

   		TRACEMSG(("cache update failed due to database error"));

		/* remove the old file */
        if(filename)
      	  {
#ifdef XP_OS2
			XP_LazyFileRemove(filename, fileType);
#else
           	XP_FileRemove(filename, fileType);
#endif
           	FREE(filename);
      	  }

		if ( !SARCache ) {
			/* close the database */
			(*local_cache_database->close)(local_cache_database);

			cache_database = 0;
		}
		else
			CACHE_CloseCache(URL_s->SARCache);

		local_cache_database = 0;
	  }
 
	if ( !SARCache )
	{
		/* add the size of the new file 
		 * size_on_disk can be in error, use content_length
		 * when missized
		 */
		net_DiskCacheSize += net_calc_real_file_size(obj->content_length);
		net_NumberInDiskCache++;

		if (++cache_sync_count >= CACHE_SYNC_RATE) 
		{
		
			/* sync the database everytime to guarentee
			 * consistancy
			 */
			if(local_cache_database && -1 == (*local_cache_database->sync)(local_cache_database, 0))
			  {
				TRACEMSG(("Error syncing cache database"));

				/* close the database */
				(*local_cache_database->close)(local_cache_database);
				cache_database = 0;
				local_cache_database = 0;
			  }

			cache_sync_count = 0;

		}
	}
	else
	{
		/* larubbio */
		/* add the size of the new file 
		 * size_on_disk can be in error, use content_length
		 * when missized
		 */
		URL_s->SARCache->DiskCacheSize += net_calc_real_file_size(obj->content_length);
		URL_s->SARCache->NumberInDiskCache++;

	    if(URL_s->SARCache->DiskCacheSize > URL_s->SARCache->MaxSize && -1 != URL_s->SARCache->MaxSize )
		{
			TRACEMSG(("MaxSize exceeded!!!"));
			/* delete the file since we wont remember it
			 */
			XP_FileRemove(obj->filename, fileType);

			URL_s->SARCache->DiskCacheSize -= net_calc_real_file_size(obj->content_length);
			URL_s->SARCache->NumberInDiskCache--;

			return FALSE;
		}

		/* sync the database every time to guarentee
		 * consistancy
		 */
		if(local_cache_database && -1 == (*local_cache_database->sync)(local_cache_database, 0))
		  {
			TRACEMSG(("Error syncing cache database"));

			/* close the database */
			(*local_cache_database->close)(local_cache_database);
			CACHE_CloseCache(URL_s->SARCache);
			local_cache_database = 0;
		  }
		else
		{
			/* This is inefficent to do each time, but it keeps us in synch, and this is for 
			   offline browsing, which is not a performance intensive task */
			CACHE_SaveCacheInfoToDB(URL_s->SARCache);
		}
	}

	net_FreeCacheDBTdata(key);
    net_FreeCacheDBTdata(data);

	return TRUE;

}

/* Public accesor function for Netcaster */
PUBLIC Bool
NET_CacheStore(net_CacheObject * obj,  
			   URL_Struct      * URL_s, 
			   Bool		         accept_partial_files)
{
	return net_CacheStore(obj, URL_s, accept_partial_files, NEW_CACHE_STORE);
}

/* Accessor functions for cache database used by cache browser */
PUBLIC int
NET_FirstCacheObject(DBT *key, DBT *data)
{
	return (*cache_database->seq)(cache_database, key, data, R_FIRST);
}

/* Accessor functions for cache database used by cache browser */
PUBLIC int
NET_NextCacheObject(DBT *key, DBT *data)
{
	return (*cache_database->seq)(cache_database, key, data, R_NEXT);
}

/* Accessor functions for cache database used by cache browser */
PUBLIC int32
NET_GetMaxDiskCacheSize()
{
	return net_MaxDiskCacheSize;
}

/****************************************************************
 * General cache routines needed by lots of stuff
 */

typedef struct _DatePlusDBT {
	DBT    * dbt;
	time_t   date;
} DatePlusDBT;

/* removes the specified number of objects from the
 * cache taking care to remove only the oldest objects.
 */
PUBLIC int32
NET_RemoveDiskCacheObjects(uint32 remove_num)
{
#define CORRUPT_ENTRY_ARRAY_SIZE 50
	uint32 corrupt_entry_count = 0;
	DatePlusDBT *old_array;
	DBT *corrupt_entry_array[CORRUPT_ENTRY_ARRAY_SIZE];
	uint32 total_cl=0;
	uint32 total_number=0;
	time_t date, lock_date, last_modified_date;
	uint32 i,j;
	char *filename;
	char *url_address;
	DBT data;
	DBT key;

	/*
	 * This optimization is not valid for the case of URLs that normally
	 * would not be cached because their size exceeds the cache size, but
	 * will be cached anyway because the must_cache flag of the URL is set.
	 * For these URLs, the condition below would be true (since we're
	 * trying to remove as many files as possible to make room for the
	 * too-big file), which would cause all files to be removed but also
	 * the cache database to be closed and deleted.  With the database
	 * gone, net_CacheStore will fail, so even though the large file was
	 * saved correctly it won't be used.  (See bug #11327.)
	 */
#if 0
	if(remove_num >= net_NumberInDiskCache)
	  {
		net_RemoveAllDiskCacheObjects();
		return(net_DiskCacheSize);
	  }
#endif

	if(!cache_database)
		return(net_DiskCacheSize);

	old_array = (DatePlusDBT *)XP_ALLOC(sizeof(DatePlusDBT) * remove_num);
	
	if(!old_array)
		return(net_DiskCacheSize);

	XP_MEMSET(old_array, 0, sizeof(DatePlusDBT) * remove_num);

    /* We need to delete the oldest items in the database.
	 * Sequentailly go throught the DB and remove
	 * the oldest items
	 */
	
	if(0 != (*cache_database->seq)(cache_database, &key, &data, R_FIRST))
		return(net_DiskCacheSize);
	total_cl += net_calc_real_file_size(net_GetInt32InCacheDBT(&data, 
												CONTENT_LENGTH_BYTE_POSITION));
	total_number++;

	/* Only add the first item to the old_array if the number to remove
	   is greater than zero, otherwise we're writing where we shouldn't be */
	if(remove_num > 0)
	{
		old_array[0].dbt  = net_CacheDBTDup(&key);
		old_array[0].date = net_GetTimeInCacheDBT(&data, 
												  LAST_ACCESSED_BYTE_POSITION);
	}

	while(0 == (*cache_database->seq)(cache_database, &key, &data, R_NEXT))
	  {
		date = net_GetTimeInCacheDBT(&data, LAST_ACCESSED_BYTE_POSITION);

		if(date)
			last_modified_date = net_GetTimeInCacheDBT(&data,
												 LAST_MODIFIED_BYTE_POSITION);

		/* there are other items beside FAT entries in here
		 * but they don't have a date
		 */
		if(!date)
		  {
			/* this could be a corrupt entry */
			if(!net_IsValidCacheDBT(&data))
			  {
			 	if((key.size < 4 || XP_STRNCMP((char*)key.data, 
												INTERNAL_NAME_PREFIX, 
												4))
					&& corrupt_entry_count < CORRUPT_ENTRY_ARRAY_SIZE)
				  {
	
					/* store invalid cache entries in an array
					 * for later deletion
					 */
					corrupt_entry_array[corrupt_entry_count++] = 
															net_CacheDBTDup(&key);
					TRACEMSG(("Found %d corrupted cache entries", 	
							  corrupt_entry_count));
				  }
				  
			  }
		  }
		else
		  {
			url_address = net_GetAddressFromCacheKey(&key);

			if(!last_modified_date 
			   || (url_address && strncasecomp(url_address, "http",4)))
			  {
				/* this is not a http URL or it has no last modified date.
				 * Delete it preferentially
				 * if it's older than the current session
				 */
				if(NET_StartupTime > date)
				  {
					/* set the date really low so it automatically 
					 * gets deleted
					 */
					date = 1;
				  }
			  }

		    total_cl += net_calc_real_file_size(net_GetInt32InCacheDBT(&data, 
											   CONTENT_LENGTH_BYTE_POSITION));
		    total_number++;

			lock_date = net_GetTimeInCacheDBT(&data, LOCK_DATE_BYTE_POSITION);	

			/* honor locks issued during this session */
            TRACEMSG(("cache: comparing lock date %ld to startup time %ld\n",
                      lock_date, NET_StartupTime));
			if(lock_date > NET_StartupTime)
				continue;
			else if(lock_date)
				net_SetTimeInCacheDBT(&data, 
						  				LOCK_DATE_BYTE_POSITION, 
						  				0); /* current time */

		    for(i=0; i < remove_num; i++)
		      {
			    if(date < old_array[i].date || !old_array[i].date)
			      {
				    /* free the oldest one so it 
				     * doesn't get lost as it is pushed off
				     * the stack
				     */
				    if(old_array[remove_num-1].dbt)
					    net_FreeCacheDBTdata(old_array[remove_num-1].dbt);
    
				    /* shift all the entries down */
				    for(j=remove_num-1; j > i; j--)
				      {
					    old_array[j].dbt = old_array[j-1].dbt;
					    old_array[j].date = old_array[j-1].date;
				      }
    
				    old_array[i].date = date;
				    old_array[i].dbt = net_CacheDBTDup(&key);
					
					break;
			      }
	  	      }
		  }
	  }

#ifdef XP_UNIX
	/* assert(total_cl == net_DiskCacheSize); */
#endif 
	net_DiskCacheSize = total_cl;

#ifdef XP_UNIX
	/* assert(total_number == net_NumberInDiskCache); */
#endif 
	net_NumberInDiskCache = total_number;

	/* now old1 old2 and old3 should contain the oldest
	 * keys in the database.  Delete them all.
	 */
	for(i=0; i < remove_num; i++)
	  {
		DBT *db_obj = old_array[i].dbt;

		if(db_obj)
  		  {
			if(0 == (*cache_database->get)(cache_database, db_obj, &data, 0))
		 	  {
                  lock_date = net_GetTimeInCacheDBT(&data, LOCK_DATE_BYTE_POSITION);	

                  /* honor locks issued during this session */
                  TRACEMSG(("cache: comparing (2) lock date %ld to startup time %ld\n",
                            lock_date, NET_StartupTime));
                  if(lock_date > NET_StartupTime)
                  {
                      TRACEMSG(("cache: saved your ass again, bud!"));
                      continue;
                  }

				filename = net_GetFilenameInCacheDBT(&data);
				if(filename)
	  		  	  {
					TRACEMSG(("Removing file: %s due to disk"
				  			  " cache size overflow",filename));
#ifdef XP_OS2
					XP_LazyFileRemove(filename, xpCache);
#else
					XP_FileRemove(filename, xpCache);
#endif
					FREE(filename);	
	  		  	  }
				net_DiskCacheSize -= net_calc_real_file_size(
											net_GetInt32InCacheDBT(&data, 
					 						CONTENT_LENGTH_BYTE_POSITION)); 
				net_NumberInDiskCache--;
    			(*cache_database->del)(cache_database, db_obj, 0);
			  }

    		net_FreeCacheDBTdata(db_obj);
		  }
	  }

	/* remove any invalid cache intries
	 */
	for(i=0; i < corrupt_entry_count; i++)
	  {
		DBT *newkey = corrupt_entry_array[i];
    	if(newkey && -1 == (*cache_database->del)(cache_database, newkey, 0))
		  {
			TRACEMSG(("Ack, error trying to delete corrupt entry, aborting",i));
			break;
		  }

		TRACEMSG(("Deleting %d corrupted cache entries",i));
    	net_FreeCacheDBTdata(newkey);
	  } 

	/* don't need the stale array any more */
	XP_FREE(old_array);

	/* sync the database
	 */
	net_StoreDiskCacheSize();

    if(-1 == (*cache_database->sync)(cache_database, 0))
      {
        TRACEMSG(("Error syncing cache database"));

		/* close the database */
		(*cache_database->close)(cache_database);
		cache_database = 0;
      }
	      
	return(net_DiskCacheSize);
}

PRIVATE void
net_ReduceDiskCacheTo(MWContext *window_id, uint32 size, int recurse_count)
{
	uint32 avg, reduce, remove_num;

	if(net_DiskCacheSize < size 
		|| !net_NumberInDiskCache
		  || recurse_count >= 5)
		return;

	TRACEMSG(("net_ReduceDiskCacheTo: Current size: %d  "
			  "Desired Size: %d  Number: %d", 
			  net_DiskCacheSize,
			  size,
			  net_NumberInDiskCache));

	/* compute the avarage size of the
	 * contents of the disk cache
	 */
	if(net_NumberInDiskCache < 1)
		avg = 0;
	else
		avg = net_DiskCacheSize/net_NumberInDiskCache;

	/* compute the amount that we need to shrink
	 * the disk cache
	 */
	reduce = net_DiskCacheSize - size;

	/* add 10% of the total disk cache size
	 * to the reduce number to reduce
	 * the number of times that we need to
	 * do this operation
	 */
	reduce += net_DiskCacheSize/10;

	/* compute the number of objects to remove
	 */
	if(avg < 1)
		remove_num = 1;
	else
		remove_num = reduce/avg;

	if(remove_num < 1)
		remove_num = 1;

	if(window_id)
	  {
		char buffer[256];
		XP_SPRINTF(buffer, XP_GetString( XP_CACHE_CLEANUP ), remove_num);
		NET_Progress(window_id, buffer);
	  }

	NET_RemoveDiskCacheObjects(remove_num);

	/* make sure we removed enough
	 */
	if(net_DiskCacheSize > size)
		net_ReduceDiskCacheTo(window_id, size, ++recurse_count);

	return;
}

#ifdef NO_MEMORY_CACHE

/* set the size of the Memory cache.
 * Set it to 0 if you want cacheing turned off
 * completely
 */
PUBLIC void
NET_SetMemoryCacheSize(int32 new_size)
{
	
	net_MaxMemoryCacheSize=new_size;
    IL_ReduceImageCacheSizeTo(new_size);
	return;
}

PUBLIC int32
NET_GetMaxMemoryCacheSize()
{
	return net_MaxMemoryCacheSize;
}

/* remove the last memory cache object if one exists
 * returns the total size of the memory cache in bytes
 * after performing the operation
 */
PUBLIC int32
NET_RemoveLastMemoryCacheObject()
{
	/* and remove something from the image cache */
	return IL_ShrinkCache();

	/* XXX now the mac arithmetic is wrong */
	return(0);
}

/* returns the number of bytes currently in use by the Memory cache
 */
PUBLIC int32
NET_GetMemoryCacheSize()
{
    return(0);
}

/* stub functions for memory cache that are no
 * longer needed
 */
MODULE_PRIVATE int
NET_MemoryCacheLoad (ActiveEntry * cur_entry)
{
	return(-1);
}

MODULE_PRIVATE int
NET_ProcessMemoryCache (ActiveEntry * cur_entry)
{
	return(-1);
}

MODULE_PRIVATE int
NET_InterruptMemoryCache (ActiveEntry * cur_entry)
{
	return(-1);
}

#endif /* MEMORY_CACHE */

/* set the size of the Disk cache.
 * Set it to 0 if you want cacheing turned off
 * completely
 */
PUBLIC void
NET_SetDiskCacheSize(int32 new_size)
{
	if(new_size < 0)
		new_size = 0;

    net_MaxDiskCacheSize = new_size;

	/* open it if it's not open already */
    if(net_MaxDiskCacheSize > 0)
	  {
    	net_OpenCacheFatDB();
    	net_ReduceDiskCacheTo(NULL, (uint32) new_size, 0);
	  }
	else
	  {
		net_RemoveAllDiskCacheObjects();
	  }


    return;
}

/* remove the last disk cache object if one exists
 * returns the total size of the disk cache in bytes
 * after performing the operation
 */
PUBLIC int32
NET_RemoveLastDiskCacheObject(void)
{
	return(net_DiskCacheSize);
}

PRIVATE void
net_RemoveAllDiskCacheObjects(void)
{
	char * filename;
	DBT data;
	DBT key;

	if(!cache_database)
		return;

	if(0 != (*cache_database->seq)(cache_database, &key, &data, R_FIRST))
		return;

	do
	  {
    	filename = net_GetFilenameInCacheDBT(&data);     
    	if(filename)                                      
          {                                                
            TRACEMSG(("Removing file: %s due to disk"       
                      " cache size remove",filename));     
#ifdef XP_OS2
	    XP_LazyFileRemove(filename, xpCache);
#else
            XP_FileRemove(filename, xpCache);                 
#endif
            FREE(filename);                                    
          }                                                     
		/* We don't acctually need to do the
		 * del since we will del the database
		 */
        /* (*cache_database->del)(cache_database, &key, 0); */
	  }
	while(0 == (*cache_database->seq)(cache_database, &key, &data, R_NEXT));

    net_DiskCacheSize = 0;
    net_NumberInDiskCache = 0;

    if(-1 == (*cache_database->close)(cache_database))
      {
        TRACEMSG(("Error closing cache database"));
      }

	cache_database = 0;

	/* remove the database */
	XP_FileRemove("", xpCacheFAT);
}

/* returns the number of bytes currently in use by the Disk cache
 */
PUBLIC int32
NET_GetDiskCacheSize()
{
    return(net_DiskCacheSize);
}


/******************************************************************
 * Cache Stream input routines
 */

/* is the stream ready for writing?
 */
PRIVATE unsigned int net_CacheWriteReady (NET_StreamClass *stream)
{
   CacheDataObject *obj=stream->data_object;   
   if(obj->next_stream)
       return((*obj->next_stream->is_write_ready)(obj->next_stream));
   else
	   return(MAX_WRITE_READY);
}

/*  stream write function
 */
PRIVATE int net_CacheWrite (NET_StreamClass *stream, CONST char* buffer, int32 len)
{
	CacheDataObject *obj=stream->data_object;	
	TRACEMSG(("net_CacheWrite called with %ld buffer size", len));

    if(obj->fp)
	  {
		/* increase the global cache size counter
		 */
        if(XP_FileWrite((char *) buffer, len, obj->fp) < (uint32)len)
		  {
			TRACEMSG(("Error writing to cache file"));
			XP_FileClose(obj->fp);
#ifdef XP_OS2
			XP_LazyFileRemove(obj->cache_object->filename, xpCache);
#else
			XP_FileRemove(obj->cache_object->filename, xpCache);
#endif
			if(obj->URL_s)
				obj->URL_s->dont_cache = TRUE;
			obj->fp = 0;
		  }
			
	  }

    if(obj->next_stream)
	  {
    	int status=0;
	
		assert (buffer);
		assert (len > -1);
        status = (*obj->next_stream->put_block)
								(obj->next_stream, buffer, len);

		/* abort */
	    if(status < 0)
			return(status);
	  }

    return(1);
}

/*  complete the stream
*/
PRIVATE void net_CacheComplete (NET_StreamClass *stream)
{
	CacheDataObject *obj=stream->data_object;	
	/* close the cache file 
	 */
    if(obj->fp)
        XP_FileClose(obj->fp);

	assert(obj->cache_object);

	/* add it to the database */
	if(net_CacheStore(obj->cache_object, 
					  obj->URL_s, 
					  FALSE, 
					  NEW_CACHE_STORE))
	  {
	    /* set the cache_file in the url struct so that Plugins can
 	     * use it...
	     */	
	    if(obj->URL_s)
		    StrAllocCopy(obj->URL_s->cache_file, obj->cache_object->filename);
	  }

    /* complete the next stream
     */
    if(obj->next_stream)
	  {
	    (*obj->next_stream->complete)(obj->next_stream);
        FREE(obj->next_stream);
	  }

	net_freeCacheObj(obj->cache_object);
	PR_REMOVE_LINK(&obj->links);
    FREE(obj);

    return;
}

/* Abort the stream 
 * 
 */
PRIVATE void net_CacheAbort (NET_StreamClass *stream, int status)
{

	CacheDataObject *obj=stream->data_object;	
	XP_ASSERT(obj);
	XP_ASSERT(obj->cache_object);

    /* abort the next stream
     */
    if(obj->next_stream)
	  {
	    (*obj->next_stream->abort)(obj->next_stream, status);
        FREE(obj->next_stream);
	  }

	/* fp might be null if we had a file write error */
    if(obj->fp)
      {
        XP_FileClose(obj->fp);

		/* do this test in here since the file must be
		 * alright for us to use this
		 *
		 * test to see if we were user interrupted and if
		 * the server support byteranges.  If so, store
		 * the partial object since we will request it
		 * with a partial byterange later
		 *
		 * Also, we can't do partial caching unless we know
		 * the content_length
		 */
		if(obj->URL_s
			 && obj->URL_s->content_length
			  && (obj->URL_s->server_can_do_byteranges || obj->URL_s->server_can_do_restart)
				&& !obj->URL_s->dont_cache)  
		  {

			/* store the partial file */
            if(obj->URL_s->real_content_length > 0)
                obj->cache_object->content_length = obj->URL_s->real_content_length;
			net_CacheStore(obj->cache_object, 
						   obj->URL_s, 
						   TRUE, 
						   NEW_CACHE_STORE);

		  }
		else if(status == MK_INTERRUPTED 
				&& obj->URL_s->content_length
				&& !strcasecomp(obj->URL_s->content_type, TEXT_HTML))
		  {
			/* temporarly cache interrupted HTML pages for history navigation */
			obj->URL_s->last_modified = 0;
			net_CacheStore(obj->cache_object, 
						   obj->URL_s, 
						   FALSE, 
						   NEW_CACHE_STORE);
			
		  }
		else
	 	  {
#ifdef XP_OS2
		    XP_LazyFileRemove(obj->cache_object->filename, xpCache);
#else
		    XP_FileRemove(obj->cache_object->filename, xpCache);
#endif
	 	  }
      }

    net_freeCacheObj(obj->cache_object);
	PR_REMOVE_LINK(&obj->links);
    FREE(obj);

    return;
}


#ifdef XP_UNIX
extern char **fe_encoding_extensions; /* gag! */
#endif

/* setup the stream
 */
MODULE_PRIVATE NET_StreamClass * 
NET_CacheConverter (FO_Present_Types format_out,
                    void       *converter_obj,
                    URL_Struct *URL_s,
                    MWContext  *window_id)
{
    CacheDataObject * data_object=0;
    net_CacheObject * cache_object=0;
    NET_StreamClass * stream=0;
    char *filename=0, *new_filename=0;
	char *org_content_type = 0;
	Bool do_disk_cache=FALSE;
	Bool want_to_cache=FALSE;
    XP_File fp=0;
	NET_StreamClass * next_stream=0;

	/* XXX brendan will #define this hack after 3.0 ships! */
	XP_Bool dont_hold_URL_s = (converter_obj != NULL);
    
    TRACEMSG(("Setting up cache stream. Have URL: %s\n", URL_s->address));

	FE_EnableClicking(window_id);

#ifdef MOZILLA_CLIENT
    /* add this new url to the Global history since we must be getting it now
     */
	if (NET_URL_Type(URL_s->address) != WYSIWYG_TYPE_URL && URL_s->SARCache == NULL)
		GH_UpdateGlobalHistory(URL_s);
#endif /* MOZILLA_CLIENT */ 

	StrAllocCopy(org_content_type, URL_s->content_type);
	if(!org_content_type)
		return(NULL);

	if(format_out != FO_CACHE_ONLY)
	  {
		format_out = CLEAR_CACHE_BIT(format_out);
    	next_stream = NET_StreamBuilder(format_out, URL_s, window_id);
		if(!next_stream)
	   	  {
			FREEIF(org_content_type);
			return(NULL);
	 	  }
	  }

	/* set the content type back the way it should be
	 * this is because some streams muck with it and
	 * dont set it back and we need it intact
	 */
	FREE(URL_s->content_type);
	URL_s->content_type = org_content_type;
	org_content_type = 0;

    /* do we want to cache this object?
     */
    if(URL_s->server_can_do_restart) URL_s->must_cache = TRUE;


    TRACEMSG(("cache: URL_s->must_cache is %s", URL_s->must_cache ? "TRUE" : "FALSE"));

    if(URL_s->must_cache	/* overrides all the rest of these tests */
	   ||  (CLEAR_CACHE_BIT(format_out) == FO_INTERNAL_IMAGE 
			|| format_out == FO_CACHE_ONLY
			|| !strncasecomp(URL_s->content_type, "IMAGE", 5)
			|| !strncasecomp(URL_s->content_type, "TEXT", 4)
			|| !strncasecomp(URL_s->content_type, "MESSAGE", 7)
			|| !strcasecomp(URL_s->content_type, APPLICATION_HTTP_INDEX)
			|| !strcasecomp(URL_s->content_type, APPLICATION_JAVAARCHIVE)
			|| ( (URL_s->content_length>0) && ((uint32) URL_s->content_length < (net_MaxDiskCacheSize/4)) )
		   )
	   )
	  {
		/* set a flag to say that we want to cache */
		want_to_cache = TRUE;
	  }
		
	/* set a flag if we should disk cache it based
 	 * on whether the cache setting is non-zero and
	 * the cache database is open
	 */

    TRACEMSG(("cache: want_to_cache is %s", want_to_cache ? "TRUE" : "FALSE"));

	if(want_to_cache 
	   && (!net_dont_disk_cache_ssl 
			|| strncasecomp(URL_s->address, "https:", 6))
	   && !URL_s->dont_cache
	   && (net_MaxDiskCacheSize > 0 || URL_s->must_cache)
	   && net_OpenCacheFatDB() /* make sure database is open */
  	   && strncasecomp(URL_s->address, "news:", 5)   /* not news: */
	   && strncasecomp(URL_s->address, "snews:", 6)  /* not snews: */
	   && strncasecomp(URL_s->address, "mailbox://", 10))  /* not IMAP */
		do_disk_cache = TRUE;

	/* Special case for StreamAsFile plugins:  */
	if (URL_s->must_cache   /* this only set by plugin code ? */
		&& ((XP_STRNCASECMP(URL_s->address, "news:", 5)==0)      /* is news */
		||  (XP_STRNCASECMP(URL_s->address, "https:", 6)==0)     /* is secure */
		|| !strncasecomp(URL_s->address, "mailbox://", 10))) /* is imap */
		do_disk_cache = TRUE;

	/* malloc and init all the necessary structs
	 * do_disk_cache will be set false on error
	 */
	if(do_disk_cache)
      {

		TRACEMSG(("Object has not been cached before"));
			
        cache_object = XP_NEW(net_CacheObject);
        if (!cache_object)
          {
            return(NULL);
          }
    
        /* assign and init the cache object */
        XP_MEMSET(cache_object, 0, sizeof(net_CacheObject));
    
        /* copy the contents of the URL structure
         */
        StrAllocCopy(cache_object->address, URL_s->address);
        cache_object->method = URL_s->method;
        if(URL_s->post_data)
          {
			cache_object->post_data_size = URL_s->post_data_size;
            StrAllocCopy(cache_object->post_data, URL_s->post_data);
            StrAllocCopy(cache_object->post_headers, URL_s->post_headers);
          }
    
        /* add expiration stuff from URL structure
         */
	    cache_object->expires        = URL_s->expires;
	    cache_object->last_modified  = URL_s->last_modified;
    
        cache_object->content_length      = URL_s->content_length;
        cache_object->real_content_length = URL_s->content_length;
        cache_object->is_netsite     = URL_s->is_netsite;

        StrAllocCopy(cache_object->content_type, URL_s->content_type);
        StrAllocCopy(cache_object->charset, URL_s->charset);
        StrAllocCopy(cache_object->content_encoding, URL_s->content_encoding);

		/* copy security info */
		cache_object->security_on             = URL_s->security_on;
        StrAllocCopy(cache_object->key_cipher,  URL_s->key_cipher);
		cache_object->key_size                = URL_s->key_size;
		cache_object->key_secret_size         = URL_s->key_secret_size;

        StrAllocCopy(cache_object->page_services_url, URL_s->page_services_url);

		/* we always use relative paths in the main disk cache
		 */
		cache_object->is_relative_path        = TRUE;

		if (URL_s->certificate)
			cache_object->certificate =
			CERT_DupCertificate(URL_s->certificate);


        /* open a file for the cache
		   larubbio added SARCache check it's late in 4.0 cycle, and I don't want to have
           any impact on the other platforms

         */
		  filename = WH_TempName(
#ifdef XP_UNIX 
			 (NULL != URL_s->SARCache) ? xpSARCache :
#endif
			 xpCache, "cache");
		
		if (filename)  
		  {
			/* Put an appropriate suffix on the tmp cache file */
			if (cache_object->address)
			  {
				
				char *tail;
				char *suffix;
				char *allocSuffix; /* in case we have to allocate a suffix */
				char *end;
				char *junk;
				char old_char;

				suffix = allocSuffix = NULL;

				if (URL_s->content_name){
					suffix = (URL_s->content_name ? XP_STRRCHR (URL_s->content_name, '.') : 0);
				}
				else if ((URL_s->address) && 
						(!XP_STRNCASECMP(URL_s->address, "mailbox:", 8)
						|| !XP_STRNCASECMP(URL_s->address, "news:", 5)
						|| !XP_STRNCASECMP(URL_s->address, "snews:", 6))
						 && (URL_s->content_type) && (*(URL_s->content_type)))
				{
					/*
						Gag. If we're a mailbox URL and can't figure out the content name,
						but have a content type, ask the type registry for a suitable suffix. 
						(This allows us to set up Java and audio cache files so that those
						subsystems will actually work with the cache files. Ick.)
					*/
					char *regSuffix = NET_cinfo_find_ext(URL_s->content_type);
					if (regSuffix)
					{
						suffix = PR_smprintf(".%s", regSuffix);
						allocSuffix = suffix; /* we allocated it here, we delete it below */
					}
				}
				
				if (!suffix)
				{
				    tail = XP_STRRCHR (cache_object->address, '/');
					suffix = (tail ? XP_STRRCHR (tail, '.') : 0);
				}
				end = suffix + (suffix ? XP_STRLEN (suffix) : 0);
				junk=0;				
			
#ifdef XP_UNIX
				/* Gag.  foo.html.gz --> cacheXXXXX.html, not cacheXXXXX.gz. */
				if (suffix && fe_encoding_extensions)
				  {
					int i = 0;
					while (fe_encoding_extensions [i])
					  {
						if (!XP_STRCMP (suffix, fe_encoding_extensions [i]))
						  {
							end = suffix;
							suffix--;
							while (suffix > tail && *suffix != '.')
							  suffix--;
							if (*suffix != '.')
							  suffix = 0;
							break;
						  }
						i++;
					  }
				  }
#endif

				/* Avoid "cache2F1F6AD102169F0.sources&maxhits=10" */
				if (suffix)
				  {
					junk = suffix + 1;
					while (isalnum (*junk))
					  junk++;
					old_char = *junk;
					*junk = 0;
				  }


#if defined(XP_WIN) || defined(XP_OS2)               /* IBM - SAH */
				/* Remove any suffix that the temp filename currently has
				 */
				XP_STRTOK(filename, ".");
#endif

				if (suffix && (end - suffix) < 20)
				  {
					/* make sure it is terminated... */
					if(!junk)
				  	{
						junk = end;
						old_char = *junk;
						*junk = 0;
				  	}
					StrAllocCopy(new_filename, filename);
#if defined(XP_WIN) || defined(XP_OS2)               /* IBM - SAH  - even though */
                                                     /*              the comment */
                                                     /*              say 16 bit  */
                                                     /*              32 bit Win  */
                                                     /*              goes thru   */
                                                     /*              here, so do */
                                                     /*              the same for*/
                                                     /*              OS2         */
					/* make all suffixes be UPPERCASE for win16
					 * since the operating system will make 
					 * them so and we need to know the exact
					 * name for hashing and comparison purposes
					 */
					if(1)
					  {
						char *new_suffix = XP_STRDUP(suffix);
						if(new_suffix)
						  {
							char *cp = new_suffix;
							int i;
							/* limit it to 4 chars.
							 * a dot and three letters
							 */
							for(i=0; *cp && i < 4; i++)
							  {
								*cp = XP_TO_UPPER(*cp);
								cp++;
							  }
							*cp = '\0'; /* make sure it's terminated */
							StrAllocCat(new_filename, new_suffix);
							XP_FREE(new_suffix);
						  }
					  }
#else
					StrAllocCat(new_filename, suffix);
#endif
				  }
				else 
				  {
					StrAllocCopy(new_filename, filename);
				  }


				if(junk)
					*junk = old_char;

				if (allocSuffix)
					XP_FREE(allocSuffix);
			  }
			XP_FREE(filename);
		  }

		if(new_filename)
		{
			/* larubbio added 197 SAR cache checks */
			if ( URL_s->SARCache == NULL )
       			fp = XP_FileOpen(new_filename, xpCache, XP_FILE_WRITE_BIN);
			else
       			fp = XP_FileOpen(new_filename, xpSARCache, XP_FILE_WRITE_BIN);
		}
   
		if(!fp)
		  {
    	    net_freeCacheObj(cache_object);
			cache_object = 0;
			do_disk_cache = FALSE;
			FREEIF(new_filename);
		  }
		else
		  {
            cache_object->filename = new_filename;

		    /* cap the total size of the cache
		     */

			/* larubbio disk cache size checks for SARCache 
			 * Also, don't reduce the cache size yet for "must_cache"
			 * objects, since in the download-restart case the object
			 * will be deleted most of the time before it ever gets
			 * counted in the cache.  Objects that do get put into
			 * the cache will self correct at the next call to
			 * ReduceDiskCacheTo()
			 */
			if(net_DiskCacheSize+URL_s->content_length > net_MaxDiskCacheSize 
				&& URL_s->SARCache == NULL
				&& !URL_s->must_cache)
			  {
				int32 new_size = net_MaxDiskCacheSize - URL_s->content_length;
				if(new_size < 0)
					new_size = 0;
				net_ReduceDiskCacheTo(window_id, (uint32)new_size, 0);
			  }
		  }
	  }

	/* if we get this far and do_disk_cache is true
	 * then we want cache and all the setup succeeded.
	 */
	if(do_disk_cache)
	  {
		TRACEMSG(("Caching this URL!!!!"));

        data_object = XP_NEW(CacheDataObject);
        if (!data_object)
          {
			XP_FileClose(fp);
            return(NULL);
          }

        stream = XP_NEW(NET_StreamClass);
        if(!stream)
		  {
			XP_FileClose(fp);
			FREE(data_object);
            return(NULL);
		  }

        /* init the object */
        XP_MEMSET(data_object, 0, sizeof(CacheDataObject));
    
	    /* assign the cache object to the stream data object
	     */
        data_object->cache_object = cache_object;

		/* save the URL struct since we will
	 	 * need to use some data from it later
		 * XXX if converter_obj is non-null it is a flag telling us not
		 * XXX to hold a pointer to the URL struct.
		 */
		if (dont_hold_URL_s == FALSE)
			data_object->URL_s = URL_s;
        data_object->fp = fp;

    	TRACEMSG(("Returning stream from NET_CacheConverter\n"));

    	stream->name           = "Cache stream";
    	stream->complete       = (MKStreamCompleteFunc) net_CacheComplete;
    	stream->abort          = (MKStreamAbortFunc) net_CacheAbort;
    	stream->put_block      = (MKStreamWriteFunc) net_CacheWrite;
    	stream->is_write_ready = (MKStreamWriteReadyFunc) net_CacheWriteReady;
    	stream->data_object    = data_object;  /* document info object */
    	stream->window_id      = window_id;

    	if(format_out != FO_CACHE_ONLY)
            data_object->next_stream = next_stream;

		PR_APPEND_LINK(&data_object->links, &active_cache_data_objects);
    	return stream;
	  }
	else
	  {

		TRACEMSG(("NOT Disk Caching this URL"));

		if(want_to_cache)
		  {
			net_MemCacheConverterObject mem_conv_obj;

			mem_conv_obj.next_stream = next_stream;
			mem_conv_obj.dont_hold_URL_s = dont_hold_URL_s;
			return(NET_MemCacheConverter(format_out, 
										 &mem_conv_obj,
										 URL_s, 
										 window_id));
		  }

		/* bypass the whole cache mechanism
	 	 */
	    if(format_out != FO_CACHE_ONLY)
          {
          	format_out = CLEAR_CACHE_BIT(format_out);

            return(next_stream);
		  }

	  }

	return(NULL);

}

/***************************************************************************
 *   Cache object manipulation stuff
 */

/* locks or unlocks a cache file.  The 
 * cache lock is only good for a single
 * session
 */
PUBLIC Bool
NET_ChangeCacheFileLock(URL_Struct *URL_s, Bool set)
{
	int   status;
	DBT   data;
    DBT * key;
	DBT * new_data=0;
	net_CacheObject *found_cache_obj;

	/* lock or unlock the memory cache too */
	NET_ChangeMemCacheLock(URL_s, set);

	if(!cache_database)
		return(FALSE);

    key = net_GenCacheDBKey(URL_s->address,
                            URL_s->post_data,
                            URL_s->post_data_size);

    status = (*cache_database->get)(cache_database, key, &data, 0);

    if(status != 0)
      {
        TRACEMSG(("Key not found in database"));
        net_FreeCacheDBTdata(key);
        return(FALSE);
      }

	found_cache_obj = net_Fast_DBDataToCacheStruct(&data);

	if(!found_cache_obj)
	  {
		TRACEMSG(("mkcache: Corrupted db entry"));
		return(FALSE);
	  }

	if(set)
		found_cache_obj->lock_date = time(NULL);
	else
		found_cache_obj->lock_date = 0;

    /* create new dbt data object */
    new_data = net_CacheStructToDBData(found_cache_obj);

	if(new_data)
		status = (*cache_database->put)(cache_database, key, new_data, 0);

	return(status == 0);
}

MODULE_PRIVATE void NET_RefreshCacheFileExpiration(URL_Struct * URL_s)
{
    net_CacheObject *found_cache_obj;
    int  status;
    DBT *key;
    DBT  data;

    if(!cache_database)
      {
        TRACEMSG(("Cache database not open"));
        return;
      }

	/* only update if the server status is 304 
	 * The other cases involve a new cache object
	 * and all the info will automatically get updated
	 */
	if(URL_s->server_status != 304)
	  	return;

	/* gen a key */
    key = net_GenCacheDBKey(URL_s->address,
                            URL_s->post_data,
                            URL_s->post_data_size);

	/* look up the key */
    status = (*cache_database->get)(cache_database, key, &data, 0);

	/* don't need the key anymore */
    net_FreeCacheDBTdata(key);

    if(status != 0)
        return;

	/* turn the cache DB object into a cache object */
    found_cache_obj = net_Fast_DBDataToCacheStruct(&data);

    if(!found_cache_obj)
        return;

    /* stick the address and post data in there
     * since this stuff is ususally in the Key
     */
    StrAllocCopy(found_cache_obj->address, URL_s->address);
    if(URL_s->post_data_size)
      {
        found_cache_obj->post_data_size = URL_s->post_data_size;
        BlockAllocCopy(found_cache_obj->post_data,
                       URL_s->post_data,
                       URL_s->post_data_size);
      }

	/* this will update the last modified time */
	net_CacheStore(found_cache_obj, 
					NULL, 
					FALSE, 
					REFRESH_CACHE_STORE);

	return;

#if 0
	/* this is the really old version of RefreshURL */

	/* this isn't really needed because we can do it all
	 * in the Complete step by keeping a copy if the URL struct.
	 */
	int   status;
	DBT   data;
    DBT * key;
	DBT * new_data=0;

	if(!cache_database)
		return;

    key = net_GenCacheDBKey(URL_s->address,
                            URL_s->post_data,
                            URL_s->post_data_size);

    status = (*cache_database->get)(cache_database, key, &data, 0);

    if(status != 0)
      {
        TRACEMSG(("Key not found in database"));
        net_FreeCacheDBTdata(key);
        return;
      }

	new_data = net_CacheDBTDup(&data);

	if(!new_data)
	  {
    	net_FreeCacheDBTdata(key);
		return;
	  }

	net_SetTimeInCacheDBT(new_data, 
						  EXPIRES_BYTE_POSITION, 
						  URL_s->expires);
	net_SetTimeInCacheDBT(new_data, 
						  LAST_MODIFIED_BYTE_POSITION, 
						  URL_s->last_modified);
	net_SetTimeInCacheDBT(new_data, 
						  LAST_ACCESSED_BYTE_POSITION, 
						  time(NULL)); /* current time */

    TRACEMSG(("Refreshing the cache expires date: %d  last_mod: %d\n",
									URL_s->expires, URL_s->last_modified));

	
	status = (*cache_database->put)(cache_database, key, new_data, 0);

#ifdef DEBUG
	if(status != 0)
		TRACEMSG(("Error updateing cache info in database"));
#endif /* DEBUG */

    net_FreeCacheDBTdata(key);

    net_FreeCacheDBTdata(new_data);

	return;
#endif
}

/* returns TRUE if the url is in the disk cache
 */
PUBLIC Bool
NET_IsURLInDiskCache(URL_Struct *URL_s)
{
	DBT *key;
	DBT data;
	int status;

	if(!cache_database)
	  {
		TRACEMSG(("Cache database not open"));
	 	return(0); 
	  }

	key = net_GenCacheDBKey(URL_s->address, 
							URL_s->post_data, 
							URL_s->post_data_size);

	status = (*cache_database->get)(cache_database, key, &data, 0);

    net_FreeCacheDBTdata(key);
	
	if(status == 0)
		return(TRUE);
	else
		return(FALSE);
}

/* remove a URL from the cache
 */
MODULE_PRIVATE void
NET_RemoveURLFromCache(URL_Struct *URL_s)
{

	int status;
	DBT data;
	DBT *key;

	/* larubbio */
	Bool		SARCache = FALSE;
	XP_FileType fileType;
	DB			*local_cache_database = NULL;

	/* larubbio fix to allow 197 SAR cache adds without modifying 
	 * the navigator cache (too much)
	 */
	if ( URL_s != NULL && URL_s->SARCache != NULL )
	{
		SARCache = TRUE;
		fileType = xpSARCache;
		local_cache_database = URL_s->SARCache->database;
	}
	else
	{
		fileType = xpCache;
		local_cache_database = cache_database;
	}


	NET_RemoveURLFromMemCache(URL_s);

	if(!local_cache_database)
		return;

	key = net_GenCacheDBKey(URL_s->address, 
								URL_s->post_data, 
								URL_s->post_data_size);

	if(!key)
		return;

	status = (*local_cache_database->get)(local_cache_database, key, &data, 0);

	if(status == 0)
 	  {
        char * filename = net_GetFilenameInCacheDBT(&data);

		if(filename)
		  {
#ifdef XP_OS2
			XP_LazyFileRemove(filename, fileType);
#else
			XP_FileRemove(filename, fileType);
#endif
			FREE(filename);
		  }

		(*local_cache_database->del)(local_cache_database, key, 0);

		if ( SARCache )
		{
			/* subtract the size of the file being removed */
        	URL_s->SARCache->DiskCacheSize -= net_calc_real_file_size(
											    net_GetInt32InCacheDBT(&data,
												CONTENT_LENGTH_BYTE_POSITION));
			URL_s->SARCache->NumberInDiskCache--;
		}
		else
		{
        	net_DiskCacheSize -= net_calc_real_file_size(
											    net_GetInt32InCacheDBT(&data,
												CONTENT_LENGTH_BYTE_POSITION));
			net_NumberInDiskCache--;
		}
	  }

	net_FreeCacheDBTdata(key);
}

/* return TRUE if the URL is in the cache and
 * is a partial cache file
 */
MODULE_PRIVATE Bool
NET_IsPartialCacheFile(URL_Struct *URL_s)
{
	net_CacheObject *found_cache_obj;
	int  status;
	DBT *key;
	DBT  data;

    if(!cache_database)
      {
        TRACEMSG(("Cache database not open"));
        return(0);
      }

	key = net_GenCacheDBKey(URL_s->address, 
							URL_s->post_data, 
							URL_s->post_data_size);

	status = (*cache_database->get)(cache_database, key, &data, 0);

	net_FreeCacheDBTdata(key);

	if(status != 0)
		return(0);
		
	found_cache_obj = net_Fast_DBDataToCacheStruct(&data);

	if(found_cache_obj)
		return(found_cache_obj->incomplete_file);
	else
		return(0);
}

PUBLIC int
NET_FindURLInCache(URL_Struct * URL_s, MWContext *ctxt)
{
	net_CacheObject *found_cache_obj;
    XP_StatStruct    stat_entry;
	int   status;
	char *byterange;
	char  byterange_char;
	DBT  *key;
	DBT   data;
	TRACEMSG(("Checking for URL in cache"));

	/* zero the last modified date so that we don't
	 * screw up the If-modified-since header by
     * having it in even when the document isn't
	 * cached.
	 */
	URL_s->last_modified = 0;

	if(!cache_database)
	  {
		TRACEMSG(("Cache database not open"));

		/* can't find it here.  Let's look
		 * in the memory and external caches
		 */
		status = NET_FindURLInMemCache(URL_s, ctxt);
	
		if(status)
			return(status);
		else
			return(NET_FindURLInExtCache(URL_s, ctxt));
	  }

    byterange = strcasestr(URL_s->address, ";bytes=");
    
    /* this might be a cached imap url.  Ignore the mime
       part when going back for attachments, otherwise we'll
       reload the attachment from the server */
    if (!byterange)
    	byterange = strcasestr(URL_s->address, "&part=");

    if(byterange)
      {
        byterange_char = *byterange;
        *byterange = '\0';
      }

	key = net_GenCacheDBKey(URL_s->address, 
							URL_s->post_data, 
							URL_s->post_data_size);
	

    if(byterange)
        *byterange = byterange_char;

	status = (*cache_database->get)(cache_database, key, &data, 0);


	/* try it again with the byterange stuff
	 */
	if(status != 0 && byterange)
	  {
		net_FreeCacheDBTdata(key);
		key = net_GenCacheDBKey(URL_s->address, 
								URL_s->post_data, 
								URL_s->post_data_size);
		status = (*cache_database->get)(cache_database, key, &data, 0);
	  }

	/* If the file doesn't have an extension try adding
	 * a slash to the end and look for it again.
	 * this will catch the most common redirect cases
 	 */
	if(status != 0)
	  {
		char *slash;

		slash = XP_STRRCHR(URL_s->address, '/');
		if(slash && !XP_STRCHR(slash, '.') && *(slash+1) != '\0')
		  {
			/* no extension.  Add the slash and
			 * look for it in the database
			 */
			StrAllocCat(URL_s->address, "/");
			net_FreeCacheDBTdata(key);
			key = net_GenCacheDBKey(URL_s->address, 
									URL_s->post_data, 
									URL_s->post_data_size);
			status = (*cache_database->get)(cache_database, key, &data, 0);

			if(status != 0)
			  {
				/* adding the slash didn't work, get rid of it 
			 	 */
				URL_s->address[XP_STRLEN(URL_s->address)-1] = '\0';
			  }
		  }
	  }

	/* If the java Cache api is requesting this file, and it was found in the
	 * navigator cache, remove it from that cache so it can be recached in the proper archive cache
	 */
	if( (0 == status) && (NULL != URL_s->SARCache) )
	  {
		found_cache_obj = net_Fast_DBDataToCacheStruct(&data);

		if(!found_cache_obj)
		{
			TRACEMSG(("mkcache: Corrupted db entry"));
			return(0);
		}

		/* remove the object from the cache
		 */
		XP_FileRemove(found_cache_obj->filename, xpCache);

		if(cache_database)
			status = (*cache_database->del)(cache_database, key, 0);

		/* remove the size of that file from the running total */
        net_DiskCacheSize -= net_calc_real_file_size(
											found_cache_obj->content_length);

		status = -1;
	  }


	if(status != 0)
	  {
		TRACEMSG(("Key not found in database"));
		net_FreeCacheDBTdata(key);

		/* can't find it here.  Let's look
		 * in the memory and external caches
		 */
		status = NET_FindURLInMemCache(URL_s, ctxt);
	
		if(status)
			return(status);
		else
			return(NET_FindURLInExtCache(URL_s, ctxt));
	  }

	found_cache_obj = net_Fast_DBDataToCacheStruct(&data);

	if(!found_cache_obj)
	  {
		TRACEMSG(("mkcache: Corrupted db entry"));
		return(0);
	  }

    TRACEMSG(("mkcache: found URL in cache!"));

	/* pretend we don't have the object cached if we are looking
	 * for a byterange URL and the object is only partially
	 * cached
	 */
	if(byterange && found_cache_obj->incomplete_file)
	  {
		return(0);
	  }

	/* make sure the file still exists
	 */
	if(XP_Stat(found_cache_obj->filename, &stat_entry, xpCache) == -1)
	  {
		/* file does not exist!!
		 * remove the entry 
		 */
		TRACEMSG(("Error! Cache file missing"));

		if(cache_database)
			status = (*cache_database->del)(cache_database, key, 0);

		/* remove the size of that file from the running total */
        net_DiskCacheSize -= net_calc_real_file_size(
											found_cache_obj->content_length);

#ifdef DEBUG
		if(status != 0)
			TRACEMSG(("Error deleting bad file from cache"));
#endif /* DEBUG */

	    net_FreeCacheDBTdata(key);

		return(0);
			
	  }

    /*
     * if the last accessed date is before the startup date set the
     * expiration date really low so that the URL is forced to be rechecked
     * again.  We don't just not return the URL as being in the
     * cache because we want to use "If-modified-since"
	 *
     * This works correctly because mkhttp will zero the
	 * expires field.
	 *
	 * if it's not an http url then just delete the entry
	 * since we can't do an If-modified-since
	 *
	 * NOTE: use the use_local_copy flag as a signal not to
 	 * do the "once per session" check since it is possible
	 * that the cache is read only, also use history_num
	 * since we shouldn't reload on history navigation or resize
     */
    if(!URL_s->use_local_copy)
	  {
	    if(found_cache_obj->last_accessed < NET_StartupTime
		    && NET_CacheUseMethod != CU_NEVER_CHECK 
#ifdef MOZ_OFFLINE          
			&& !NET_IsOffline() 
#endif /* MOZ_OFFLINE */
         )							/* *X* check for is offline */
	      {
		    if(!strncasecomp(URL_s->address, "http", 4))
		      {
        	    URL_s->expires = 1;

#if 0
	/* this is ifdeffed because of the following bug:
	 * If you update now, there is still the possibility
	 * of putting this URL on the wait queue.  If that
	 * happens, when the URL gets pulled off the wait queue
	 * it will be called with NET_GetURL again and come
 	 * here again.  Since we already updated it wont
	 * get set expired again.  So instead I will update
	 * the URL when I get a 304 in NET_RefreshCacheFileExpiration
	 */
				/* stick the address and post data in there
				 * since this stuff is in the Key
				 */
				StrAllocCopy(found_cache_obj->address, URL_s->address);
				if(URL_s->post_data_size)
				  {
					found_cache_obj->post_data_size = URL_s->post_data_size;
					BlockAllocCopy(found_cache_obj->post_data,
							   		URL_s->post_data, 
							   		URL_s->post_data_size);
				  }
				
				/* this will update the last accessed time
				 */
				net_CacheStore(found_cache_obj, 
							   NULL, 
							   FALSE, 
							   REFRESH_CACHE_STORE);
#endif
		      }
		    else
		      {
			    /* remove the object from the cache
			     */
#ifdef XP_OS2
			    XP_LazyFileRemove(found_cache_obj->filename, xpCache);
#else
			    XP_FileRemove(found_cache_obj->filename, xpCache);
#endif
			    /* remove the size of that file from the running total */
        	    net_DiskCacheSize -= net_calc_real_file_size(
											found_cache_obj->content_length);
    
			    status = (*cache_database->del)(cache_database, key, 0);
#ifdef DEBUG
			    if(status != 0)
				    TRACEMSG(("Error deleting expired file from cache"));
#endif /* DEBUG */

	    	    net_FreeCacheDBTdata(key);

			    return(0);
		      }
	      }
	    else
	      {
		    /* otherwise use the normal expires date */
 		    URL_s->expires = found_cache_obj->expires;
	      }
	  }
	else
	  {
 		URL_s->expires = 0; /* prevent looping */
	  }

	/* we don't need the key anymore */
	net_FreeCacheDBTdata(key);

	/* copy in the cache file name
	 */
	StrAllocCopy(URL_s->cache_file, found_cache_obj->filename);

    /* copy the contents of the URL struct so that the content type
     * and other stuff gets recorded
     */
	if(!URL_s->preset_content_type)
    	StrAllocCopy(URL_s->content_type,     found_cache_obj->content_type);
    StrAllocCopy(URL_s->charset,          found_cache_obj->charset);
    StrAllocCopy(URL_s->content_encoding, found_cache_obj->content_encoding);

    StrAllocCopy(URL_s->page_services_url, found_cache_obj->page_services_url);

	/* only set these for HTTP url's
	 * otherwise the content-length will mess up files that don't
	 * return a content-length and are different
	 */
	if(!strncasecomp(URL_s->address, "http", 4) || !strncasecomp(URL_s->address, "ftp", 3))
	  {
    	URL_s->content_length = found_cache_obj->content_length;
   		URL_s->real_content_length = found_cache_obj->real_content_length;

	    if(URL_s->content_length != URL_s->real_content_length
	       && !found_cache_obj->incomplete_file)
	      {
		    XP_ASSERT(0);  

			URL_s->real_content_length = 0;
			URL_s->content_length = 0;

		    /* content type indicates a partial cache file but
		     * the incomplete file flag is not set.  Something
		     * went wrong.
		     */
		    return(0);  /* return not found */
	  }
	  }

 	URL_s->last_modified  = found_cache_obj->last_modified;
 	URL_s->is_netsite     = found_cache_obj->is_netsite;

	/* copy security info */
	URL_s->security_on             = found_cache_obj->security_on;
	URL_s->key_size                = found_cache_obj->key_size;
	URL_s->key_secret_size         = found_cache_obj->key_secret_size;
	StrAllocCopy(URL_s->key_cipher,  found_cache_obj->key_cipher);

	/* free any existing certificate first */
    CERT_DestroyCertificate(URL_s->certificate);
	URL_s->certificate = NULL;
	
	if (found_cache_obj->certificate)
		  URL_s->certificate =
			CERT_DupCertificate(found_cache_obj->certificate);

	TRACEMSG(("Cached copy is valid. returning method"));

	TRACEMSG(("Using Disk Copy"));

	return(FILE_CACHE_TYPE_URL);
}


/* read the Cache File allocation table.
 */
PUBLIC void
NET_ReadCacheFAT(char * cachefatfile, Bool stat_files)
{
    if(net_MaxDiskCacheSize > 0)
    	net_OpenCacheFatDB();

	return;
}

/* unload the disk cache FAT list to disk
 *
 * set final_call to true if this is the last call to
 * this function before cleaning up the cache and shutting down.
 *
 * Front ends should never set final_call to TRUE, this will
 * be done by the cache cleanup code when the netlib is being
 * shutdown.
 */
PUBLIC void
NET_WriteCacheFAT(char *filename, Bool final_call)
{
	net_StoreDiskCacheSize();

	if(!cache_database)
        return;

    if(-1 == (*cache_database->sync)(cache_database, 0))
      {
        TRACEMSG(("Error syncing cache database"));
		/* close the database */
		(*cache_database->close)(cache_database);
		cache_database = 0;
      }

	return;
}

MODULE_PRIVATE void
NET_CleanupCache (char * filename)
{
	net_StoreDiskCacheSize();

	if(!cache_database)
        return;

    if(-1 == (*cache_database->close)(cache_database))
      {
        TRACEMSG(("Error closing cache database"));
      }

	cache_database = 0;

	CACHE_CloseAllOpenSARCache();

	return;
}

/* returns the number of files currently in the
 * disk cache
 */
PUBLIC uint32
NET_NumberOfFilesInDiskCache()
{
	return(net_NumberInDiskCache);
}

static int
net_cache_strcmp (const void *a, const void *b)
{
  return XP_STRCMP ((const char *) a, (const char *) b);
}


/* this function recurses through all directories in
 * the cache directory and stuffs all the files into
 * the hash table.  Any directory names below the
 * root cache directory should have their relative path 
 * names prepended to them
 *
 * returns 0 on success, -1 on failure
 */
PRIVATE int
net_cache_recursive_file_finder(XP_HashList *hash_table, 
								const char *prefix,
								char *cur_dir, 
								char *base_dir,
								char *buffer)
{
	XP_Dir dir_ptr;
   	XP_DirEntryStruct * dir_entry;
	int base_len;
	int prefix_len, d_len, status;
	char *dir_prefix=0;
	char *d_name;
	Bool add_dir_prefix=TRUE;

	/* compute the difference between base_dir and
	 * cur_dir.  The difference should be prepended
	 * to the front of filenames
	 */
	base_len = XP_STRLEN(base_dir);
	StrAllocCopy(dir_prefix, cur_dir+base_len);

	if(!dir_prefix)
		return(-1);


	if(*dir_prefix == '\0')
		add_dir_prefix = FALSE;
	else if(dir_prefix[XP_STRLEN(dir_prefix)-1] == '/')
		dir_prefix[XP_STRLEN(dir_prefix)-1] = '\0';

    if(!(dir_ptr = XP_OpenDir (cur_dir, xpCache)))
	{
		FREE(dir_prefix);
		return(-1);
	}

	/* add all the files on disk to a hash table
	 */
	prefix_len = XP_STRLEN(prefix);
    while((dir_entry = XP_ReadDir(dir_ptr)) != 0)
      {
		/* only do comparison and delete if the prefix
		 * is the same
		 */
		d_name = dir_entry->d_name;
		d_len = XP_STRLEN(d_name);
		status = 0;
		if(XP_STRNCMP(d_name, prefix, prefix_len))
	 	  {
#if 0
    		XP_StatStruct stat_entry;
#endif

			/* This directory didn't begin with the cache prefix, so it
			   *should* be a directory.  But if it's not, then it's some
			   random junk that somehow got into the cache directory.

			   Rather than stat'ing it first and then recursing, we will
			   simply assume it's a directory, recurse, and call XP_OpenDir
			   on it.  If it's not a directory this will fail, and we will
			   ignore it.
			 */

#ifdef XP_UNIX
			/* Unix-specific speed hack: only recursively descend directories
			   if they have names which consist of exactly two hex digits.
			   Because of the behavior of xp_file.c on Unix, anything else is
			   either not a directory, or not a directory which we use.
			 */
			if (! (d_len == 2 &&
				   ((d_name[0] >= '0' && d_name[0] <= '9') ||
					(d_name[0] >= 'A' && d_name[0] <= 'F')) &&
				   ((d_name[1] >= '0' && d_name[1] <= '9') ||
					(d_name[1] >= 'A' && d_name[1] <= 'F'))))
			  continue;
#else /* !XP_UNIX */
			/* This is redundant on Unix because of the above check. */
			if(!XP_STRCMP(d_name, ".") || !XP_STRCMP(d_name, ".."))
			  {
				/* ignore . and .. */
				status = -1;
			  }
#endif /* !XP_UNIX */

			else if(add_dir_prefix)
		  	  {
				XP_SPRINTF(buffer, "%.250s/%.250s", dir_prefix, d_name);
		  	  }

    		if(status != -1)
		  	  {
				char *new_dir=0;
				StrAllocCopy(new_dir, cur_dir);
				if(cur_dir[XP_STRLEN(cur_dir)-1] != '/')
					StrAllocCat(new_dir, "/");
				StrAllocCat(new_dir, d_name);
				net_cache_recursive_file_finder(hash_table, 
												prefix,
												new_dir, 
												base_dir,
												buffer);
				FREE(new_dir);
		  	  }
			continue;
		  }

		if(add_dir_prefix)
		  {
			XP_SPRINTF(buffer, "%.250s/%.250s", dir_prefix, d_name);
			XP_HashListAddObject(hash_table, XP_STRDUP(buffer));
		  }
		else
		  {
			XP_HashListAddObject(hash_table, XP_STRDUP(d_name));
		  }
		
	  }

	FREE(dir_prefix);

	XP_CloseDir(dir_ptr);
	return 0;
}
/* cleans up the cache directory by listing every
 * file and deleting the ones it doesn't know about
 * that begin with the cacheing prefix
 */
PUBLIC int
NET_CleanupCacheDirectory(char * dir_name, const char * prefix)
{
#define REMOVE_LIST_SIZE 1000
	DBT *remove_list[REMOVE_LIST_SIZE];
	char *filename, *d_name;
	XP_HashList *hash_table;
	DBT key, data;
	int status;
	char buffer[512];
	uint32 num_files;
	int i;
	int number_in_remove_list=0;
#ifdef XP_MAC

	/* make sure the size of the cache is as we want it
	 */
	if(net_MaxDiskCacheSize == 0)
		net_RemoveAllDiskCacheObjects();
	else
    	net_ReduceDiskCacheTo(NULL, net_MaxDiskCacheSize, 0);

	if(!dir_name)
		dir_name = "";

	/* optimization for cleanup.
	 *
	 * If the number of files on disk is within a couple
	 * of the number of files that we know about then
	 * skip the cache directory cleanup
	 */
	num_files = XP_FileNumberOfFilesInDirectory(dir_name, xpCache);
    if(num_files-3 <= net_NumberInDiskCache)
	    return(0);
#endif 

	if(!cache_database)
		return(-1);

	TRACEMSG(("cleaning up cache directory of unknown cache files"));

	hash_table = XP_HashListNew(1597, XP_StringHash, net_cache_strcmp);

	if(!hash_table)
		return(-1);
	
	/* find all the files in the cache directory 
	 */
	if(net_cache_recursive_file_finder(hash_table, 
									   prefix, 
									   dir_name, 
									   dir_name, 
									   buffer))
		return(-1);

	/* init the count of all the files in the database */
	num_files = 0;

	/* now run through the disk cache FAT and remove
	 * any matches from the hash table
	 */
    status = (*cache_database->seq)(cache_database, &key, &data, R_FIRST);

    while(status == 0)
      {
        filename = net_GetFilenameInCacheDBT(&data);
        if(filename)
          {
			time_t last_modified_date = net_GetTimeInCacheDBT(&data, 
												LAST_MODIFIED_BYTE_POSITION);

			if(!last_modified_date)
			  {
				/* if there is no last_modified date then
				 * it doesn't do us much good to keep the
				 * data since it can't be compared and used
				 * in a future session.  Put the file in a remove list
				 *
				 * Don't do this if the check mode is NEVER
				 *
				 */
		    	if(NET_CacheUseMethod != CU_NEVER_CHECK
				   && number_in_remove_list < REMOVE_LIST_SIZE)
					remove_list[number_in_remove_list++] = 
														net_CacheDBTDup(&key);
			  }
			else
			  {
				num_files++; /* keep a count of all the files in the database */

				d_name = (char*) XP_HashListRemoveObject(hash_table, filename);
				if(d_name)
			  	  {
					FREE(d_name);
			  	  }
			  }
            FREE(filename);
          }
	    else
		  {
			/* could not grab the filename from
			 * the DBT.  See if this is a valid
			 * entry
			 */
			if(!net_IsValidCacheDBT(&data)
				&& (key.size < 4 || XP_STRNCMP((char*)key.data,
                                                INTERNAL_NAME_PREFIX,
                                                4))
                && number_in_remove_list < REMOVE_LIST_SIZE)
			  {
				remove_list[number_in_remove_list++] = net_CacheDBTDup(&key);
			  }
		  }	

    	status = (*cache_database->seq)(cache_database, &key, &data, R_NEXT);
      }

	/* any files left in the hash table are not in the
	 * cache FAT, so delete them
	 *
	 * @@@@ Manipulate the hash table directly
	 */
	{
		int list_count;
		XP_List *list_ptr;
		
		for(list_count=0; list_count < hash_table->size; list_count++)
      	  {
        	list_ptr = hash_table->list[list_count];
        	if(list_ptr)
          	  {
            	while((filename = 
						    (char *) XP_ListRemoveTopObject(list_ptr)) != 0)
              	  {
#ifdef XP_OS2
					XP_LazyFileRemove(filename, xpCache);
#else
					XP_FileRemove(filename, xpCache);
#endif
					TRACEMSG(("Unknown cache file %s found! -- deleteing...", 
														filename));
					FREE(filename);
              	  }
            	XP_ListDestroy(list_ptr);  /* free the list */
          	  }
      	  }

		XP_HashListDestroy(hash_table);
	}

	/* remove the entries that are no longer needed or
	 * are corrupt
	 *
	 * Note that we don't need to remove the files since
	 * they will already have been removed from the code
	 * above
	 */
    TRACEMSG(("Deleting %d unneeded cache entries",number_in_remove_list));
    for(i=0; i < number_in_remove_list; i++)
      {
        DBT *newkey = remove_list[i];
        if(-1 == (*cache_database->del)(cache_database, newkey, 0))
          {
            TRACEMSG(("Ack, error trying to delete corrupt entry, aborting",i));            break;
          }

        net_FreeCacheDBTdata(newkey);
      }

	/* if the number of files that we counted in the database
	 * does not agree with the number we think it should be
	 * reset the number we think it should be with the actual
	 * number
	 */
    if(net_NumberInDiskCache != num_files)
      {
		TRACEMSG(("Reseting number of files in cache to: %d", num_files));
        net_NumberInDiskCache = num_files;
	    net_StoreDiskCacheSize();  /* this will store the number of files */
	  }
	return(0);
}

/* removes all cache files from the cache directory
 * and deletes the cache FAT
 */
PUBLIC int
NET_DestroyCacheDirectory(char * dir_name, char * prefix)
{
	net_RemoveAllDiskCacheObjects();
	NET_CleanupCacheDirectory(dir_name, prefix);

    (*cache_database->close)(cache_database);
    cache_database = 0;

	net_DiskCacheSize=0;
	net_NumberInDiskCache=0;
	
    return XP_FileRemove("", xpCacheFAT);
}

/* create an HTML stream and push a bunch of HTML about
 * the cache
 */
MODULE_PRIVATE void 
NET_DisplayCacheInfoAsHTML(ActiveEntry * cur_entry)
{
	char *buffer = (char*)XP_ALLOC(2048);
	char *address;
	char *escaped;
   	NET_StreamClass * stream;
	net_CacheObject * cache_obj;
	DBT key, data;
	Bool long_form = FALSE;
	int i;

	if(!buffer)
	  {
		cur_entry->status = MK_UNABLE_TO_CONVERT;
		return;
	  }

	if(strcasestr(cur_entry->URL_s->address, "?long"))
		long_form = TRUE;
	else if(strcasestr(cur_entry->URL_s->address, "?traceon"))
		NET_CacheTraceOn = TRUE;
	else if(strcasestr(cur_entry->URL_s->address, "?traceoff"))
		NET_CacheTraceOn = FALSE;

	StrAllocCopy(cur_entry->URL_s->content_type, TEXT_HTML);

	cur_entry->format_out = CLEAR_CACHE_BIT(cur_entry->format_out);
	stream = NET_StreamBuilder(cur_entry->format_out, 
							   cur_entry->URL_s, 
							   cur_entry->window_id);

	if(!stream)
	  {
		cur_entry->status = MK_UNABLE_TO_CONVERT;
		FREE(buffer);
		return;
	  }


	/* define a macro to push a string up the stream
	 * and handle errors
	 */
#define PUT_PART(part)													\
cur_entry->status = (*stream->put_block)(stream,			\
										part ? part : "Unknown",		\
										part ? XP_STRLEN(part) : 7);	\
if(cur_entry->status < 0)												\
  goto END;

	XP_SPRINTF(buffer, 
"<TITLE>Information about the Netscape disk cache</TITLE>\n"
"<h2>Disk Cache statistics</h2>\n"
"<TABLE>\n"
"<TR>\n"
"<TD ALIGN=RIGHT><b>Maximum size:</TD>\n"
"<TD>%ld</TD>\n"
"</TR>\n"
"<TR>\n"
"<TD ALIGN=RIGHT><b>Current size:</TD>\n"
"<TD>%ld</TD>\n"
"</TR>\n"
"<TR>\n"
"<TD ALIGN=RIGHT><b>Number of files in cache:</TD>\n"
"<TD>%ld</TD>\n"
"</TR>\n"
"<TR>\n"
"<TD ALIGN=RIGHT><b>Average cache file size:</TD>\n"
"<TD>%ld</TD>\n"
"</TR>\n"
"</TABLE>\n"
"<HR>",
net_MaxDiskCacheSize,
net_DiskCacheSize,
net_NumberInDiskCache,
net_NumberInDiskCache ? net_DiskCacheSize/net_NumberInDiskCache : 0);

	PUT_PART(buffer);

	if(!cache_database)
	  {
		XP_STRCPY(buffer, "The cache database is currently closed");
		PUT_PART(buffer);
		goto END;
	  }

    if(0 != (*cache_database->seq)(cache_database, &key, &data, R_FIRST))
        return;

	/* define some macros to help us output HTML tables
	 */
#if 0

#define TABLE_TOP(arg1)				\
	XP_SPRINTF(buffer, 				\
"<TR><TD ALIGN=RIGHT><b>%s</TD>\n"	\
"<TD>", arg1);						\
PUT_PART(buffer);

#define TABLE_BOTTOM				\
	XP_SPRINTF(buffer, 				\
"</TD></TR>");						\
PUT_PART(buffer);

#else

#define TABLE_TOP(arg1)					\
	XP_STRCPY(buffer, "<tt>");			\
	for(i=XP_STRLEN(arg1); i < 16; i++)	\
		XP_STRCAT(buffer, "&nbsp;");	\
	XP_STRCAT(buffer, arg1);			\
	XP_STRCAT(buffer, " </tt>");		\
	PUT_PART(buffer);

#define TABLE_BOTTOM					\
	XP_STRCPY(buffer, "<BR>\n");		\
	PUT_PART(buffer);

#endif

    do
      {

		cache_obj = net_Fast_DBDataToCacheStruct(&data);

        if(!cache_obj)
		  {
			if(!net_IsValidCacheDBT(&data)
			 	&& (key.size < 4 || XP_STRNCMP((char*)key.data, 
												INTERNAL_NAME_PREFIX, 
												4)))
			  {
				
				XP_STRCPY(buffer, "<H3>Corrupted or misversioned cache entry</H3>"
								  "This entry will be deleted automatically "
								  "in the future."
								  "<HR ALIGN=LEFT WIDTH=50%>");
				PUT_PART(buffer);
			  }
		  }
		else
          {
#if 0
			/* begin a table for this entry */
			XP_STRCPY(buffer, "<TABLE>");
			PUT_PART(buffer);
#endif

			/* put the URL out there */
			/* the URL is 8 bytes into the key struct
         	 */
			address = (char *)key.data+8;

			TABLE_TOP("URL:");
			XP_STRCPY(buffer, "<A TARGET=Internal_URL_Info HREF=about:");
			PUT_PART(buffer);
			PUT_PART(address);
			XP_STRCPY(buffer, ">");
			PUT_PART(buffer);
			escaped = NET_EscapeHTML(address);
			PUT_PART(escaped);
			FREE(escaped);
			XP_STRCPY(buffer, "</A>");
			PUT_PART(buffer);
			TABLE_BOTTOM;

			TABLE_TOP( XP_GetString(MK_CACHE_CONTENT_LENGTH) );
			XP_SPRINTF(buffer, "%lu", cache_obj->content_length);
			PUT_PART(buffer);
			TABLE_BOTTOM;

			if(cache_obj->content_length != cache_obj->real_content_length)
			  {
				TABLE_TOP( XP_GetString(MK_CACHE_REAL_CONTENT_LENGTH) );
				XP_SPRINTF(buffer, "%lu", cache_obj->real_content_length);
				PUT_PART(buffer);
				TABLE_BOTTOM;
			  }

			TABLE_TOP( XP_GetString(MK_CACHE_CONTENT_TYPE) );
			PUT_PART(cache_obj->content_type);
			TABLE_BOTTOM;

			TABLE_TOP( XP_GetString(MK_CACHE_LOCAL_FILENAME) );
			PUT_PART(cache_obj->filename);
			TABLE_BOTTOM;

			TABLE_TOP( XP_GetString(MK_CACHE_LAST_MODIFIED) );
			if(cache_obj->last_modified)
			  {
				PUT_PART(ctime(&cache_obj->last_modified));
			  }
			else
			  {
				XP_STRCPY(buffer, "No date sent");
				PUT_PART(buffer);
			  }
			TABLE_BOTTOM;

			TABLE_TOP( XP_GetString(MK_CACHE_EXPIRES) );
			if(cache_obj->expires)
			  {
				PUT_PART(ctime(&cache_obj->expires));
			  }
			else
			  {
				XP_STRCPY(buffer, "No expiration date sent");
				PUT_PART(buffer);
			  }
			TABLE_BOTTOM;

			if(long_form)
			  {
				TABLE_TOP( XP_GetString(MK_CACHE_LAST_ACCESSED) );
				PUT_PART(ctime(&cache_obj->last_accessed));
				TABLE_BOTTOM;

				TABLE_TOP( XP_GetString(MK_CACHE_CHARSET) );
				if(cache_obj->charset)
				  {
					PUT_PART(cache_obj->charset);
				  }
				else
				  {
					XP_STRCPY(buffer, "iso-8859-1 (default)");
					PUT_PART(buffer);
				  }
				TABLE_BOTTOM;

				TABLE_TOP( XP_GetString(MK_CACHE_SECURE) );
				XP_SPRINTF(buffer, "%s", cache_obj->security_on ? 
													"TRUE" : "FALSE");
				PUT_PART(buffer);
				TABLE_BOTTOM;

				TABLE_TOP( XP_GetString(MK_CACHE_USES_RELATIVE_PATH) );
				XP_SPRINTF(buffer, "%s", cache_obj->is_relative_path ? 
													"TRUE" : "FALSE");
				PUT_PART(buffer);
				TABLE_BOTTOM;

				TABLE_TOP( XP_GetString(MK_CACHE_FROM_NETSITE_SERVER) );
				XP_SPRINTF(buffer, "%s", cache_obj->is_netsite ? 
													"TRUE" : "FALSE");
				PUT_PART(buffer);
				TABLE_BOTTOM;

				TABLE_TOP( "Lock date:" );
				if(cache_obj->lock_date)
			  	{
					PUT_PART(ctime(&cache_obj->lock_date));
			  	}
				else
			  	{
					XP_STRCPY(buffer, "Not locked");
					PUT_PART(buffer);
			  	}
				TABLE_BOTTOM;
			  }
				

#if 0
			/* end the table for this entry */
			XP_STRCPY(buffer, "</TABLE><HR ALIGN=LEFT WIDTH=50%>");
			PUT_PART(buffer);
#else
			XP_STRCPY(buffer, "\n<P>\n");
			PUT_PART(buffer);
#endif
          }
	
	
      }
    while(0 == (*cache_database->seq)(cache_database, &key, &data, R_NEXT));

END:
	FREE(buffer);
	if(cur_entry->status < 0)
		(*stream->abort)(stream, cur_entry->status);
	else
		(*stream->complete)(stream);

	return;
}

#include "libmocha.h"

#define SANE_BUFLEN	1024

NET_StreamClass *
NET_CloneWysiwygCacheFile(MWContext *window_id, URL_Struct *URL_s,
			  uint32 nbytes, const char * wysiwyg_url, 
			  const char * base_href)
{
	char *filename;
	PRCList *link;
	CacheDataObject *data_object;
	NET_StreamClass *stream;
	XP_File fromfp;
	int32 buflen, len;
	char *buf;
    int url_type = NET_URL_Type(URL_s->address);
	XP_FileType mode = xpCache;
    
    if (url_type == MAILBOX_TYPE_URL ||
        url_type == IMAP_TYPE_URL ||
        url_type == POP3_TYPE_URL)
        return NULL;

    filename = URL_s->cache_file;   
	if (!filename) 
	  {
		/* not hitting the cache -- check whether we're filling it */
		for (link = active_cache_data_objects.next;
			 link != &active_cache_data_objects;
			 link = link->next)
		  {
			data_object = (CacheDataObject *)link;
			if (data_object->URL_s == URL_s)
			  {
				XP_FileFlush(data_object->fp);
				filename = data_object->cache_object->filename;
				goto found;
			  }
		  }

		/* try memory cache and local filesystem */
		stream = net_CloneWysiwygMemCacheEntry(window_id, URL_s, nbytes,
						       wysiwyg_url, base_href);
		if (stream)
			return stream;
		return net_CloneWysiwygLocalFile(window_id, URL_s, nbytes, 
						 wysiwyg_url, base_href);
	  }
	else
	  {
		/* 
		 * If this is a local file URL_s->cache_file will have been
		 *   set by the code in mkfile.c so that the stream is treated
		 *   like a disk-cache stream.  But, in this case, we care that
		 *   they are different and need to preserve the fact that its
		 *   a local file URL
		 */
		if(NET_IsLocalFileURL(URL_s->address))
			mode = xpURL;
	  }

found:
	fromfp = XP_FileOpen(filename, mode, XP_FILE_READ_BIN);
	if (!fromfp)
		return NULL;
	stream = LM_WysiwygCacheConverter(window_id, URL_s, wysiwyg_url, 
					  base_href);
	if (!stream)
	  {
		XP_FileClose(fromfp);
		return 0;
	  }
	buflen = stream->is_write_ready(stream);
	if (buflen > SANE_BUFLEN)
		buflen = SANE_BUFLEN;
	buf = (char *)XP_ALLOC(buflen * sizeof(char));
	if (!buf)
	  {
		XP_FileClose(fromfp);
		return 0;
	  }
	while (nbytes != 0)
	  {
		len = buflen;
		if ((uint32)len > nbytes)
			len = (int32)nbytes;
		len = XP_FileRead(buf, len, fromfp);
		if (len <= 0)
			break;
		if (stream->put_block(stream, buf, len) < 0)
			break;
		nbytes -= len;
	  }
	FREE(buf);
	XP_FileClose(fromfp);
	if (nbytes != 0)
	  {
		/* NB: Our caller must clear top_state->mocha_write_stream. */
		stream->abort(stream, MK_UNABLE_TO_CONVERT);
		XP_DELETE(stream);
		return 0;
	  }
	return stream;
}

#endif /* MOZILLA_CLIENT */
