/* -*- 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 "mkcache.h" #ifdef MOZILLA_CLIENT #include "mktrace.h" #include "prmem.h" #include "plstr.h" /* 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 , July-95. * Modifications/Addition: Gagan Saksena, 97 */ #include "rosetta.h" #ifndef EXT_DB_ROUTINES #include "secnav.h" #include "sechash.h" #endif #include "extcache.h" /* include this for everything */ #ifdef EXT_DB_ROUTINES #include #include #include typedef struct { int32 len; char *data; } SECItem; #ifdef _sgi #include #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 = PR_NEW(DBT); if(!rv) return(NULL); rv->size = obj->size; rv->data = PR_Malloc(rv->size); if(!rv->data) { PR_Free(rv); return(NULL); } memcpy(rv->data, obj->data, rv->size); return(rv); } /* free the cache object */ MODULE_PRIVATE void net_freeCacheObj (net_CacheObject * cache_obj) { PR_FREEIF(cache_obj->address); PR_FREEIF(cache_obj->post_data); PR_FREEIF(cache_obj->post_headers); PR_FREEIF(cache_obj->content_type); PR_FREEIF(cache_obj->charset); PR_FREEIF(cache_obj->content_encoding); PR_FREEIF(cache_obj->page_services_url); PR_FREEIF(cache_obj->filename); #ifndef EXT_DB_ROUTINES PR_FREEIF(cache_obj->sec_info); #endif PR_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 PRBool 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 = PR_NEW(DBT); if(!rv) return(NULL); total_size = sizeof(net_CacheObject); #define ADD_STRING_SIZE(string) \ total_size += old_obj->string ? PL_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); total_size += sizeof(uint32); /* size of secinfo */ total_size += HG73653(old_obj->sec_info); ADD_STRING_SIZE(page_services_url); #undef ADD_STRING_SIZE new_obj = PR_Malloc(total_size * sizeof(char)); if(!new_obj) { PR_Free(rv); return NULL; } 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; * XP_Bool is_netsite; * * time_t lock_date; * * char * filename; * int32 filename_len; * * int32 HG52242; * unsigned char * HG42320; * * 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; * * XP_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 ? PL_strlen(old_obj->string)+1 : 0); \ COPY_INT32((void *)cur_ptr, &len); \ cur_ptr = cur_ptr + sizeof(int32); \ if(len) \ 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); HG73209 /* 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); #ifndef EXT_DB_ROUTINES HG42539 /* save the security info */ if ( old_obj->sec_info ) { len = HG65293(old_obj->sec_info); COPY_INT32((void *)cur_ptr, &len); cur_ptr = cur_ptr + sizeof(int32); memcpy((void *)cur_ptr, old_obj->sec_info, len); cur_ptr += len; } 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) { 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 = PR_NEW(net_CacheObject); char * cur_ptr; char * max_ptr; uint32 len; int32 version; if(!rv) return NULL; 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*)PR_Malloc(len); \ if(!rv->string) \ { \ net_freeCacheObj(rv); \ return(NULL); \ } \ 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")); PR_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)); PR_Free(rv); return(NULL); } HG32839 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); HG72761 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 = PR_Malloc(rv->post_data_size+1); if(rv->post_data) 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; 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); 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); memset(&test, 0, sizeof(net_CacheObject)); StrAllocCopy(test.address, "test3"); StrAllocCopy(test.charset, "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 %d %d %s\n", rv->address, rv->charset, 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 = PR_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) { PR_ASSERT(0); rv->size = 0; return(rv); } hash = PL_strchr(address, '#'); /* don't include '#' in a key */ if(hash) *hash = '\0'; str_len = PL_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 */ HG42490 rv->size = size; rv->data = PR_Malloc(size); if(!rv->data) { PR_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); HG83777 /* 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 */ 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 */ HG73699 { 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); HG72873 /* 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 *)PR_Malloc(size); if(!rv) return(NULL); 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) { PR_Free(stuff->data); PR_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 */