/* -*- 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.
 */

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

#ifdef MOZILLA_CLIENT

/* Publicly released Netscape cache access routines.
 *
 * These routines are shared between the netscape executable
 * and the programs released as a cache developers kit.
 *
 * Created: Lou Montulli <montulli@netscape.com>, July-95.
 * Modifications/Addition: Gagan Saksena, 97
 */

#ifndef EXT_DB_ROUTINES
#include "cert.h"
#include "sechash.h"
#endif

#include "extcache.h"  /* include this for everything */

#ifdef EXT_DB_ROUTINES

#include <fcntl.h>
#include <sys/stat.h>
#include <assert.h>

typedef struct {
	int32 len;
	char *data;
} SECItem;

#ifdef _sgi
#include <sys/endian.h>
#endif /* _sgi */


/* URL methods
 */
 #define URL_GET_METHOD  0
 #define URL_POST_METHOD 1
 #define URL_HEAD_METHOD 2

#endif /* DB_STORE */


MODULE_PRIVATE DBT *
net_CacheDBTDup(DBT *obj)
{
	DBT * rv = XP_NEW(DBT);

	if(!rv)
		return(NULL);

	rv->size = obj->size;
	rv->data = XP_ALLOC(rv->size);

	if(!rv->data)
	  {
		FREE(rv);
		return(NULL);
	  }

	XP_MEMCPY(rv->data, obj->data, rv->size);

	return(rv);

}

/* free the cache object
 */
MODULE_PRIVATE void net_freeCacheObj (net_CacheObject * cache_obj)
{

    FREEIF(cache_obj->address);
    FREEIF(cache_obj->post_data);
    FREEIF(cache_obj->post_headers);
    FREEIF(cache_obj->content_type);
    FREEIF(cache_obj->charset);
    FREEIF(cache_obj->content_encoding);
    FREEIF(cache_obj->page_services_url);
    FREEIF(cache_obj->filename);
	
#ifndef EXT_DB_ROUTINES
	if ( cache_obj->certificate ) {
		CERT_DestroyCertificate(cache_obj->certificate);
	}
#endif
	
    FREE(cache_obj);
}

/* returns true if this DBT looks like a valid
 * entry.  It looks at the checksum and the
 * version number to see if it's valid
 */
#define MAX_VALID_DBT_SIZE 10000

MODULE_PRIVATE Bool
net_IsValidCacheDBT(DBT *obj)
{
    char *cur_ptr, *max_ptr;
    uint32 len;

	if(!obj || obj->size < 9 || obj->size > MAX_VALID_DBT_SIZE)
		return(FALSE);

    cur_ptr = (char *)obj->data;
    max_ptr = cur_ptr+obj->size;

    /* get the total size of the struct out of
     * the first field to check it
     */
    COPY_INT32(&len, cur_ptr);
    cur_ptr += sizeof(int32);

    if(len != obj->size)
      {
        TRACEMSG(("Size going in is not the same as size coming out"));
        return(FALSE);
      }

    /* get the version number of the written structure
     */
    if(cur_ptr > max_ptr)
        return(FALSE);
    COPY_INT32(&len, cur_ptr);
    cur_ptr += sizeof(int32);

    if(len != CACHE_FORMAT_VERSION)
      {
        TRACEMSG(("Version of cache structure is wrong!: %d", len));
        return(FALSE);
      }

	/* looks good to me... */
	return(TRUE);
}


/* takes a cache object and returns a malloc'd 
 * (void *) suitible for passing in as a database
 * data storage object
 */
MODULE_PRIVATE DBT *
net_CacheStructToDBData(net_CacheObject * old_obj)
{
    int32 len;
	char *cur_ptr;
	void *new_obj;
	int32 total_size;
	DBT *rv;

	rv = XP_NEW(DBT);

	if(!rv)
		return(NULL);

	total_size = sizeof(net_CacheObject);

#define ADD_STRING_SIZE(string) \
total_size += old_obj->string ? XP_STRLEN(old_obj->string)+1 : 0

	ADD_STRING_SIZE(address);
	total_size += old_obj->post_data_size+1;
	ADD_STRING_SIZE(post_headers);
	ADD_STRING_SIZE(content_type);
	ADD_STRING_SIZE(content_encoding);
	ADD_STRING_SIZE(charset);
	ADD_STRING_SIZE(filename);
	ADD_STRING_SIZE(key_cipher);
	total_size += sizeof(uint32); /* certificate length */
	ADD_STRING_SIZE(page_services_url);

#ifndef EXT_DB_ROUTINES
	if ( old_obj->certificate ) {
		total_size += old_obj->certificate->derCert.len;
	}
#endif

#undef ADD_STRING_SIZE
	
	new_obj = XP_ALLOC(total_size * sizeof(char));

	if(!new_obj)
	  {
		FREE(rv);
		return NULL;
	  }

	XP_MEMSET(new_obj, 0, total_size * sizeof(char));

	/*
	 * order is:
	 * int32	size of the entire structure;
	 *
	 * int32    version of the structure format (CACHE_FORMAT_VERSION)
	 *
     * time_t                  last_modified;
     * time_t                  last_accessed;
     * time_t                  expires;
     * uint32                  content_length;
     * Bool                    is_netsite;
	 *
 	 * time_t                  lock_date;
	 *
     * char                  * filename;
     * int32                   filename_len;
	 *
     * int32                   security_on;
     * int32                   key_size;
     * int32                   key_secret_size;             
     * char                  * key_cipher;
	 *
     * struct SECCertificateStr *certificate;
	 *
     * int32                   method;
	 *
	 * # don't store address, or post_data stuff
	 * # char                  * address;
     * # uint32                  post_data_size;
     * # char                  * post_data;
	 *
     * char                  * post_headers;
     * char                  * content_type;
     * char                  * content_encoding;
     * char                  * charset;
	 *
	 * Bool                    incomplete_file;
	 * uint32                  total_content_length;
	 *
	 * char                  * page_services_url;
 	 * 
	 * string lengths all include null terminators
	 * all integer constants are stored as 4 bytes
	 * all booleans are stored as one byte
	 */

	/* VERY VERY IMPORTANT.  Whenever the
	 * format of the record structure changes
 	 * you must verify that the byte positions	
	 * in extcache.h are updated
	 */

#define STUFF_STRING(string) 									\
{	 															\
  len = (old_obj->string ? XP_STRLEN(old_obj->string)+1 : 0);	\
  COPY_INT32((void *)cur_ptr, &len);							\
  cur_ptr = cur_ptr + sizeof(int32);							\
  if(len)														\
      XP_MEMCPY((void *)cur_ptr, old_obj->string, len);			\
  cur_ptr += len;												\
}

#define STUFF_NUMBER(number) 									\
{ 																\
  COPY_INT32((void *)cur_ptr, &old_obj->number);				\
  cur_ptr = cur_ptr + sizeof(int32);							\
}

#define STUFF_TIMET(number) 									\
{ 																\
  COPY_INT32((void *)cur_ptr, &old_obj->number);				\
  cur_ptr = cur_ptr + sizeof(time_t);							\
}

#define STUFF_BOOL(bool_val)	 									\
{ 																	\
  if(old_obj->bool_val)												\
    ((char *)(cur_ptr))[0] = 1;										\
  else																\
    ((char *)(cur_ptr))[0] = 0;										\
  cur_ptr = cur_ptr + sizeof(char);									\
}

	cur_ptr = (char *)new_obj;

	/* put the total size of the struct into
	 * the first field so that we have
	 * a cross check against corruption
	 */
  	COPY_INT32((void *)cur_ptr, &total_size);
  	cur_ptr = cur_ptr + sizeof(int32);

	/* put the version number of the structure
	 * format that we are using
	 * By using a version string when writting
	 * we can support backwards compatibility
	 * in our reading code
	 * (use "len" as a temp variable)
	 */
	len = CACHE_FORMAT_VERSION;
  	COPY_INT32((void *)cur_ptr, &len);
  	cur_ptr = cur_ptr + sizeof(int32);

	STUFF_TIMET(last_modified);
	STUFF_TIMET(last_accessed);
	STUFF_TIMET(expires);
	STUFF_NUMBER(content_length);
	STUFF_BOOL(is_netsite);

	STUFF_TIMET(lock_date);

	STUFF_STRING(filename);
	STUFF_NUMBER(filename_len);

	STUFF_BOOL(is_relative_path);

	STUFF_NUMBER(security_on);
	STUFF_NUMBER(key_size);
	STUFF_NUMBER(key_secret_size);
	STUFF_STRING(key_cipher);

#ifndef EXT_DB_ROUTINES
    /* save the certificate */
    if ( old_obj->certificate ) {
		len = old_obj->certificate->derCert.len;
		COPY_INT32((void *)cur_ptr, &len);
		cur_ptr = cur_ptr + sizeof(int32);

		XP_MEMCPY((void *)cur_ptr, old_obj->certificate->derCert.data, len);
		cur_ptr += len;
	} else {
#else
    {
#endif
		len = 0;
		COPY_INT32((void *)cur_ptr, &len);
		cur_ptr = cur_ptr + sizeof(int32);
	}

	STUFF_NUMBER(method);

#ifdef STORE_ADDRESS_AND_POST_DATA

	STUFF_STRING(address);
	STUFF_NUMBER(post_data_size);
	
	/* post_data 
	 * this is special since it not necessarily a string
	 */
	if(old_obj->post_data_size)
	  {
	    XP_MEMCPY(cur_ptr, old_obj->post_data, old_obj->post_data_size+1);
	    cur_ptr += old_obj->post_data_size+1;
	  }

#endif /* STORE_ADDRESS_AND_POST_DATA */

	STUFF_STRING(post_headers);

	STUFF_STRING(content_type);
	STUFF_STRING(content_encoding);
	STUFF_STRING(charset);

	STUFF_BOOL(incomplete_file);
	STUFF_NUMBER(real_content_length);

	STUFF_STRING(page_services_url);

#undef STUFF_STRING
#undef STUFF_NUMBER
#undef STUFF_BOOL

	rv->data = new_obj;
	rv->size = total_size;

	return(rv);

}

/* takes a database storage object and returns a malloc'd
 * cache data object.  The cache object needs all of
 * it's parts free'd.
 *
 * returns NULL on parse error 
 */
MODULE_PRIVATE net_CacheObject *
net_DBDataToCacheStruct(DBT * db_obj)
{
	net_CacheObject * rv = XP_NEW(net_CacheObject);
	char * cur_ptr;
	char * max_ptr;
	uint32 len;
	int32 version;
	SECItem seccert;

	if(!rv)
		return NULL;

	XP_MEMSET(rv, 0, sizeof(net_CacheObject));

/* if any strings are larger than this then
 * there was a serious database error
 */
#define MAX_HUGE_STRING_SIZE 10000

#define RETRIEVE_STRING(string)					\
{												\
    if(cur_ptr > max_ptr)                       \
	  {					 						\
		net_freeCacheObj(rv);				 	\
        return(NULL);                           \
	  }											\
	COPY_INT32(&len, cur_ptr);					\
	cur_ptr += sizeof(int32);					\
	if(len)										\
	  {											\
		if(len > MAX_HUGE_STRING_SIZE)			\
	      {				 						\
		    net_freeCacheObj(rv);				\
			return(NULL);						\
	      }										\
	    rv->string = (char*)XP_ALLOC(len);		\
		if(!rv->string)							\
	      {				 						\
		    net_freeCacheObj(rv);				\
			return(NULL);						\
	      }										\
	    XP_MEMCPY(rv->string, cur_ptr, len);	\
	    cur_ptr += len;							\
	  }											\
}											

#define RETRIEVE_NUMBER(number)					\
{												\
    if(cur_ptr > max_ptr)                       \
        return(rv);                             \
	COPY_INT32(&rv->number, cur_ptr);			\
	cur_ptr += sizeof(int32);					\
}

#define RETRIEVE_TIMET(number)					\
{												\
    if(cur_ptr > max_ptr)                       \
        return(rv);                             \
    COPY_INT32(&rv->number, cur_ptr);			\
	cur_ptr += sizeof(time_t);					\
}

#define RETRIEVE_BOOL(bool)				\
{										\
  if(cur_ptr > max_ptr)                 \
    return(rv);                         \
  if(((char *)(cur_ptr))[0])			\
	rv->bool = TRUE;					\
  else 									\
	rv->bool = FALSE;					\
  cur_ptr += sizeof(char);				\
}

	cur_ptr = (char *)db_obj->data;

	max_ptr = cur_ptr+db_obj->size;

	/* get the total size of the struct out of
	 * the first field to check it
	 */
	COPY_INT32(&len, cur_ptr);
	cur_ptr += sizeof(int32);

	if(len != db_obj->size)
	  {
		TRACEMSG(("Size going in is not the same as size coming out"));
		FREE(rv);
		return(NULL);
	  }

	/* get the version number of the written structure
	 */
    if(cur_ptr > max_ptr)
        return(rv);
	COPY_INT32(&version, cur_ptr);
	cur_ptr += sizeof(int32);

	if(version != CACHE_FORMAT_VERSION)
	  {
		TRACEMSG(("Version of cache structure is wrong!: %d", version));
		FREE(rv);
		return(NULL);
	  }

	RETRIEVE_TIMET(last_modified);
	RETRIEVE_TIMET(last_accessed);
	RETRIEVE_TIMET(expires);
	RETRIEVE_NUMBER(content_length);
	RETRIEVE_BOOL(is_netsite);

	RETRIEVE_TIMET(lock_date);

	RETRIEVE_STRING(filename);
	RETRIEVE_NUMBER(filename_len);

	RETRIEVE_BOOL(is_relative_path);

	RETRIEVE_NUMBER(security_on);
	RETRIEVE_NUMBER(key_size);
	RETRIEVE_NUMBER(key_secret_size);
	RETRIEVE_STRING(key_cipher);

    /* certificate length */
    if(cur_ptr > max_ptr)
        return(rv);
	COPY_INT32(&len, cur_ptr);
	cur_ptr += sizeof(int32);

    seccert.len = len;

    if ( len ) {
		/* copy certificate */
		if(cur_ptr > max_ptr)
			return(rv);

		seccert.data = (unsigned char *)XP_ALLOC(len);
		if ( !seccert.data )
			return(rv);

		XP_MEMCPY(seccert.data, cur_ptr, len);
		cur_ptr += len;

#ifndef EXT_DB_ROUTINES
		rv->certificate = CERT_NewTempCertificate(CERT_GetDefaultCertDB(),
												  &seccert, NULL, PR_FALSE,
												  PR_TRUE);
		XP_FREE(seccert.data);
#endif
	}

	RETRIEVE_NUMBER(method);

#ifdef STORE_ADDRESS_AND_POST_DATA

	RETRIEVE_STRING(address);
	RETRIEVE_NUMBER(post_data_size);
	
	/* post_data 
	 * this is special since it not necessarily a string
	 */
	if(rv->post_data_size)
	  {
		rv->post_data = XP_ALLOC(rv->post_data_size+1);
		if(rv->post_data)
			XP_MEMCPY(rv->post_data, cur_ptr, rv->post_data_size+1);
		cur_ptr += rv->post_data_size+1;
	  }

#endif /* STORE_ADDRESS_AND_POST_DATA */

	RETRIEVE_STRING(post_headers);

	RETRIEVE_STRING(content_type);
	RETRIEVE_STRING(content_encoding);
	RETRIEVE_STRING(charset);

	RETRIEVE_BOOL(incomplete_file);
	RETRIEVE_NUMBER(real_content_length);

	RETRIEVE_STRING(page_services_url);
#undef RETRIEVE_STRING
#undef RETRIEVE_NUMBER
#undef RETRIEVE_BOOL

	return(rv);
}

#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

/* generates a key for use in the cache database
 * from a CacheObject struct
 * 
 * Key is based on the address and the post_data
 *
 * looks like:
 *  size checksum | size of address | ADDRESS | size of post data | POST DATA
 */
MODULE_PRIVATE DBT *
net_GenCacheDBKey(char *address, char *post_data, int32 post_data_size)
{
	DBT *rv = XP_NEW(DBT);
	char *hash;
	char *data_ptr;
	int32 str_len;
	int32 size;

#define MD5_HASH_SIZE 16 /* always 16 due to md5 hash type */
	
	if(!rv)
		return(NULL);
	
	if(!address)
	  {
		XP_ASSERT(0);
		rv->size = 0;
		return(rv);
	  } 

	hash = XP_STRCHR(address, '#');

	/* don't include '#' in a key */
	if(hash)
		*hash = '\0';

	str_len = XP_STRLEN(address)+1;

	size  = sizeof(int32);  /* for check sum */
	size += sizeof(int32);  /* for size of address */
	size += str_len;        /* for address string */
	size += sizeof(int32);  /* for size of post_data */

	if(post_data_size)
		size += MD5_HASH_SIZE;

	rv->size = size;
	rv->data = XP_ALLOC(size);

	if(!rv->data)
	  {
		FREE(rv);
		return NULL;
	  }

	data_ptr = (char *) rv->data;

	/* put in the checksum size */
	COPY_INT32(data_ptr, &size);
	data_ptr = data_ptr + sizeof(int32);
	
	/* put in the size of the address string */
	COPY_INT32(data_ptr, &str_len);
	data_ptr = data_ptr + sizeof(int32);

	/* put in the address string data */
	XP_MEMCPY(data_ptr, address, str_len);
	data_ptr = data_ptr + str_len;

	/* set the address back to it's original form */
	if(hash)
		*hash = '#';

	/* put in the size of the post data */
	if(post_data_size)
	  {
		int32 size_of_md5 = MD5_HASH_SIZE;
		unsigned char post_data_hash[MD5_HASH_SIZE];

		MD5_HashBuf(post_data_hash, (unsigned char*)post_data, post_data_size);

		COPY_INT32(data_ptr, &size_of_md5);
		data_ptr = data_ptr + sizeof(int32);

		/* put in the post data if there is any */
		XP_MEMCPY(data_ptr, post_data_hash, sizeof(post_data_hash));
	  }
	else
	  {
		COPY_INT32(data_ptr, &post_data_size);
		data_ptr = data_ptr + sizeof(int32);
	  }

	return(rv);
}

/* returns a static string that contains the
 * URL->address of the key
 * 
 * returns NULL on error
 */
MODULE_PRIVATE char *
net_GetAddressFromCacheKey(DBT *key)
{
	uint32 size;
	char *data;

	/* check for minimum size */
	if(key->size < 10)
		return(NULL);

	/* validate size checksum */
	data = (char *)key->data;
	COPY_INT32(&size, data);
	data += sizeof(int32);

	if(size != key->size)
		return(NULL);

	/* get size of address string */
	COPY_INT32(&size, data);
	data += sizeof(int32);

	/* make sure it's a valid c string */
	if(data[size] != '\0')
		return(NULL);
		
	/* it's valid return it */
	return(data);
}


/* checks a date within a DBT struct so
 * that we don't have to convert it into a CacheObject
 *
 * This works because of the fixed length record format
 * of the first part of the specific DBT format I'm
 * using
 * 
 * returns 0 on error
 */
MODULE_PRIVATE time_t 
net_GetTimeInCacheDBT(DBT *data, int byte_position)
{
	time_t date;
	char *ptr = (char *)data->data;

	if(data->size < byte_position+sizeof(time_t))
		return(0);

	if(!net_IsValidCacheDBT(data))
		return(0);

	COPY_INT32(&date, ptr+byte_position);

	/* TRACEMSG(("Got date from cache DBT: %d", date)); */
	
	return(date);

}

/* Sets a date within a DBT struct so
 * that we don't have to convert it into a CacheObject
 *
 * This works because of the fixed length record format
 * of the first part of the specific DBT format I'm
 * using
 * 
 * returns 0 on error
 */
MODULE_PRIVATE void
net_SetTimeInCacheDBT(DBT *data, int byte_position, time_t date)
{
	char *ptr = (char *)data->data;

    if(data->size < byte_position+sizeof(time_t))
        return;

	if(!net_IsValidCacheDBT(data))
		return;

    COPY_INT32(ptr+byte_position, &date);

    return;

}

/* Gets the filename within a cache DBT struct so
 * that we don't have to convert it into a CacheObject
 *
 * This works because of the fixed length record format
 * of the first part of the specific DBT format I'm
 * using
 *
 * returns NULL on error
 */
#define MAX_FILE_SIZE 2048
MODULE_PRIVATE char *
net_GetFilenameInCacheDBT(DBT *data)
{
	int32 size;
	char *rv;
	char *ptr = (char*)data->data;

    if(data->size < FILENAME_BYTE_POSITION)
        return(NULL);

	if(!net_IsValidCacheDBT(data))
		return(0);

    COPY_INT32(&size, ptr+FILENAME_SIZE_BYTE_POSITION);

    if(data->size < FILENAME_BYTE_POSITION+size 
		|| size > MAX_FILE_SIZE)
        return(NULL);

	rv = (char *)XP_ALLOC(size);
	if(!rv)
		return(NULL);
    XP_MEMCPY(rv, ptr+FILENAME_BYTE_POSITION, size);

	TRACEMSG(("Got filename: %s from DBT", rv));

    return(rv);
}

/* Gets a int32 within a DBT struct so
 * that we don't have to convert it into a CacheObject
 *
 * This works because of the fixed length record format
 * of the first part of the specific DBT format I'm
 * using
 *
 * returns 0 on error
 */
MODULE_PRIVATE time_t
net_GetInt32InCacheDBT(DBT *data, int byte_position)
{
    int32 num;
	char *ptr = (char *)data->data;

	if(!net_IsValidCacheDBT(data))
		return(0);

    if(data->size < byte_position+sizeof(time_t))
        return(0);

    COPY_INT32(&num, ptr+byte_position);

    /* TRACEMSG(("Got int32 from cache DBT: %d", num)); */
   
    return(num);

}

MODULE_PRIVATE void
net_FreeCacheDBTdata(DBT *stuff)
{
	if(stuff)
	  {
		FREE(stuff->data);
		FREE(stuff);
	  }
}

/* takes a database storage object and returns an un-malloc'd
 * cache data object. The structure returned has pointers
 * directly into the database memory and are only valid
 * until the next call to any database function
 *
 * do not free anything returned by this structure
 */
MODULE_PRIVATE net_CacheObject *
net_Fast_DBDataToCacheStruct(DBT *obj)
{
	static net_CacheObject *rv=0;

	/* free any previous one */
	if(rv)
		net_freeCacheObj(rv);

	rv = net_DBDataToCacheStruct(obj);

	return(rv);

}

#endif /* MOZILLA_CLIENT */
