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

#define alphabetize 1

#include "nsCookie.h"
#include "nsString.h"
#include "nsIServiceManager.h"
#include "nsFileStream.h"
#include "nsIFileLocator.h"
#include "nsIFileSpec.h"
#include "nsFileLocations.h"
#include "nsINetSupportDialogService.h"
#include "nsIIOService.h"
#include "nsIURL.h"
#include "nsIStringBundle.h"
#include "nsVoidArray.h"

#include "nsIPref.h"
#include "nsTextFormatter.h"
#include "xp_core.h"

extern "C" {
#include "prmon.h"
#ifdef XP_MAC
#include "prpriv.h"             /* for NewNamedMonitor */
#else
#include "private/prpriv.h"
#endif
}

static NS_DEFINE_IID(kIFileLocatorIID, NS_IFILELOCATOR_IID);
static NS_DEFINE_CID(kFileLocatorCID, NS_FILELOCATOR_CID);
static NS_DEFINE_CID(kNetSupportDialogCID, NS_NETSUPPORTDIALOG_CID);
static NS_DEFINE_IID(kIIOServiceIID, NS_IIOSERVICE_IID);
static NS_DEFINE_CID(kIOServiceCID, NS_IOSERVICE_CID);
static NS_DEFINE_IID(kIStringBundleServiceIID, NS_ISTRINGBUNDLESERVICE_IID);
static NS_DEFINE_IID(kStringBundleServiceCID, NS_STRINGBUNDLESERVICE_CID);

#define MAX_NUMBER_OF_COOKIES 300
#define MAX_COOKIES_PER_SERVER 20
#define MAX_BYTES_PER_COOKIE 4096  /* must be at least 1 */
#ifndef MAX_HOST_NAME_LEN
#define MAX_HOST_NAME_LEN 64
#endif

#define image_behaviorPref "network.image.imageBehavior"
#define image_warningPref "network.image.warnAboutImages"
#define cookie_behaviorPref "network.cookie.cookieBehavior"
#define cookie_warningPref "network.cookie.warnAboutCookies"
#define cookie_strictDomainsPref "network.cookie.strictDomains"
#define cookie_lifetimePref "network.cookie.lifetimeOption"
#define cookie_lifetimeValue "network.cookie.lifetimeLimit"
#define cookie_localization "chrome://communicator/locale/wallet/cookie.properties"
#define COOKIE_IS_SPACE(x) ((((unsigned int) (x)) > 0x7f) ? 0 : isspace(x))

//#define GET_ALL_PARTS              127
#define GET_PASSWORD_PART           64
#define GET_USERNAME_PART           32
#define GET_PROTOCOL_PART          16
#define GET_HOST_PART               8
#define GET_PATH_PART               4
#define GET_HASH_PART               2
#define GET_SEARCH_PART             1

MODULE_PRIVATE time_t 
cookie_ParseDate(char *date_string);

MODULE_PRIVATE char * 
cookie_ParseURL (const char *url, int parts_requested);

MODULE_PRIVATE char *
XP_StripLine (char *string);

typedef struct _cookie_CookieStruct {
  char * path;
  char * host;
  char * name;
  char * cookie;
  time_t expires;
  time_t lastAccessed;
  PRBool xxx;
  PRBool isDomain;   /* is it a domain instead of an absolute host? */
} cookie_CookieStruct;

#define COOKIEPERMISSION 0
#define IMAGEPERMISSION 1
#define NUMBER_OF_PERMISSIONS 2
typedef struct _permission_HostStruct {
  char * host;
  nsVoidArray * permissionList;
} permission_HostStruct;

typedef struct _permission_TypeStruct {
  PRInt32 type;
  PRBool permission;
} permission_TypeStruct;

typedef struct _cookie_DeferStruct {
  char * curURL;
  char * setCookieHeader;
  time_t timeToExpire;
} cookie_DeferStruct;

#include "prthread.h"
#include "prmon.h"

typedef enum {
  COOKIE_Normal,
  COOKIE_Discard,
  COOKIE_Trim,
  COOKIE_Ask
} COOKIE_LifetimeEnum;

PRBool cookie_SetCookieStringInUse = PR_FALSE;
PRIVATE PRBool cookie_cookiesChanged = PR_FALSE;
PRIVATE PRBool cookie_permissionsChanged = PR_FALSE;
PRIVATE PRBool cookie_rememberChecked;
PRIVATE PRBool image_rememberChecked;
PRIVATE COOKIE_BehaviorEnum cookie_behavior = COOKIE_Accept;
PRIVATE PRBool cookie_warning = PR_FALSE;
PRIVATE COOKIE_BehaviorEnum image_behavior = COOKIE_Accept;
PRIVATE PRBool image_warning = PR_FALSE;
PRIVATE COOKIE_LifetimeEnum cookie_lifetimeOpt = COOKIE_Normal;
PRIVATE time_t cookie_lifetimeLimit = 90*24*60*60;

PRIVATE nsVoidArray * cookie_cookieList=0;
PRIVATE nsVoidArray * cookie_permissionList=0;
PRIVATE nsVoidArray * cookie_deferList=0;

static PRMonitor * cookie_cookieLockMonitor = NULL;
static PRThread * cookie_cookieLockOwner = NULL;
static int cookie_cookieLockCount = 0;

static PRMonitor * cookie_deferLockMonitor = NULL;
static PRThread  * cookie_deferLockOwner = NULL;
static int cookie_deferLockCount = 0;

#define REAL_DIALOG 1

static
time_t
get_current_time()
    /*
      We call this routine instead of |time()| because the latter returns
      different values on the Mac than on all other platforms (i.e., based on
      the Mac's 1900 epoch, vs all others 1970).  We can't call |PR_Now|
      directly, since the value is 64bits and too hard to manipulate.
      Hence, this cross-platform convenience routine.
    */
  {
    PRInt64 usecPerSec;
    LL_I2L(usecPerSec, 1000000L);

    PRTime now_useconds = PR_Now();

    PRInt64 now_seconds;
    LL_DIV(now_seconds, now_useconds, usecPerSec);

    time_t current_time_in_seconds;
    LL_L2I(current_time_in_seconds, now_seconds);

    return current_time_in_seconds;
  }

/* StrAllocCopy and StrAllocCat should really be defined elsewhere */
#include "plstr.h"
#include "prmem.h"
#undef StrAllocCopy
#define StrAllocCopy(dest, src) Local_SACopy (&(dest), src)
char *
Local_SACopy(char **destination, const char *source)
{
  if(*destination) {
    PL_strfree(*destination);
    *destination = 0;
  }
  *destination = PL_strdup(source);
  return *destination;
}

#undef StrAllocCat
#define StrAllocCat(dest, src) Local_SACat (&(dest), src)
char *
Local_SACat(char **destination, const char *source)
{
  if (source && *source) {
    if (*destination) {
      int length = PL_strlen (*destination);
      *destination = (char *) PR_Realloc(*destination, length + PL_strlen(source) + 1);
      if (*destination == NULL) {
        return(NULL);
      }
      PL_strcpy (*destination + length, source);
    } else {
      *destination = PL_strdup(source);
    }
  }
  return *destination;
}

PRIVATE PRUnichar*
cookie_Localize(char* genericString) {
  nsresult ret;
  nsAutoString v;

  /* create a URL for the string resource file */
  nsIIOService* pNetService = nsnull;
  ret = nsServiceManager::GetService(kIOServiceCID, kIIOServiceIID,
    (nsISupports**) &pNetService);

  if (NS_FAILED(ret)) {
    printf("cannot get net service\n");
    return v.ToNewUnicode();
  }
  nsIURI *url = nsnull;

  nsIURI *uri = nsnull;
  ret = pNetService->NewURI(cookie_localization, nsnull, &uri);
  if (NS_FAILED(ret)) {
    printf("cannot create URI\n");
    nsServiceManager::ReleaseService(kIOServiceCID, pNetService);
    return v.ToNewUnicode();
  }

  ret = uri->QueryInterface(NS_GET_IID(nsIURI), (void**)&url);
  NS_RELEASE(uri);
  nsServiceManager::ReleaseService(kIOServiceCID, pNetService);

  if (NS_FAILED(ret)) {
    printf("cannot create URL\n");
    return v.ToNewUnicode();
  }

  /* create a bundle for the localization */
  nsIStringBundleService* pStringService = nsnull;
  ret = nsServiceManager::GetService(kStringBundleServiceCID,
    kIStringBundleServiceIID, (nsISupports**) &pStringService);
  if (NS_FAILED(ret)) {
    printf("cannot get string service\n");
    NS_RELEASE(url);
    return v.ToNewUnicode();
  }
  nsILocale* locale = nsnull;
  nsIStringBundle* bundle = nsnull;
  char* spec = nsnull;
  ret = url->GetSpec(&spec);
  NS_RELEASE(url);
  if (NS_FAILED(ret)) {
    printf("cannot get url spec\n");
    nsServiceManager::ReleaseService(kStringBundleServiceCID, pStringService);
    nsCRT::free(spec);
    return v.ToNewUnicode();
  }
  ret = pStringService->CreateBundle(spec, locale, &bundle);
  nsCRT::free(spec);
  if (NS_FAILED(ret)) {
    printf("cannot create instance\n");
    nsServiceManager::ReleaseService(kStringBundleServiceCID, pStringService);
    return v.ToNewUnicode();
  }
  nsServiceManager::ReleaseService(kStringBundleServiceCID, pStringService);

  /* localize the given string */
  nsString   strtmp; strtmp.AssignWithConversion(genericString);
  const PRUnichar *ptrtmp = strtmp.GetUnicode();
  PRUnichar *ptrv = nsnull;
  ret = bundle->GetStringFromName(ptrtmp, &ptrv);
  v = ptrv;
  NS_RELEASE(bundle);
  if (NS_FAILED(ret)) {
    printf("cannot get string from name\n");
    return v.ToNewUnicode();
  }
  nsCRT::free(ptrv);
  return v.ToNewUnicode();
}

PRBool
cookie_CheckConfirmYN(nsIPrompt *aPrompter, PRUnichar * szMessage, PRUnichar * szCheckMessage, PRBool* checkValue) {

  nsresult res;
  nsCOMPtr<nsIPrompt> dialog;

  if (aPrompter)
    dialog = aPrompter;
  else
    dialog = do_GetService(kNetSupportDialogCID);
  if (!dialog) {
    *checkValue = 0;
    return PR_FALSE;
  }

  PRInt32 buttonPressed = 1; /* in case user exits dialog by clickin X */
  PRUnichar * yes_string = cookie_Localize("Yes");
  PRUnichar * no_string = cookie_Localize("No");
  PRUnichar * confirm_string = cookie_Localize("Confirm");

  nsString tempStr; tempStr.AssignWithConversion("chrome://global/skin/question-icon.gif");
  res = dialog->UniversalDialog(
    NULL, /* title message */
    confirm_string, /* title text in top line of window */
    szMessage, /* this is the main message */
    szCheckMessage, /* This is the checkbox message */
    yes_string, /* first button text */
    no_string, /* second button text */
    NULL, /* third button text */
    NULL, /* fourth button text */
    NULL, /* first edit field label */
    NULL, /* second edit field label */
    NULL, /* first edit field initial and final value */
    NULL, /* second edit field initial and final value */
    tempStr.GetUnicode() ,
    checkValue, /* initial and final value of checkbox */
    2, /* number of buttons */
    0, /* number of edit fields */
    0, /* is first edit field a password field */
    &buttonPressed);

  if (NS_FAILED(res)) {
    *checkValue = 0;
  }
  if (*checkValue!=0 && *checkValue!=1) {
    *checkValue = 0; /* this should never happen but it is happening!!! */
  }
  Recycle(yes_string);
  Recycle(no_string);
  Recycle(confirm_string);
  return (buttonPressed == 0);

#ifdef yyy
  /* following is an example of the most general usage of UniversalDialog */
  PRUnichar* inoutEdit1 = nsString("Edit field1 initial value").GetUnicode();
  PRUnichar* inoutEdit2 = nsString("Edit field2 initial value").GetUnicode();
  PRBool inoutCheckbox = PR_TRUE;
  PRInt32 buttonPressed;

  res = dialog->UniversalDialog(
    nsString("Title Message").GetUnicode(),
    nsString("Dialog Title").GetUnicode(),
    nsString("This is the main message").GetUnicode(),
    nsString("This is the checkbox message").GetUnicode(),
    nsString("First Button").GetUnicode(),
    nsString("Second Button").GetUnicode(),
    nsString("Third Button").GetUnicode(),
    nsString("Fourth Button").GetUnicode(),
    nsString("First Edit field").GetUnicode(),
    nsString("Second Edit field").GetUnicode(),
    &inoutEdit1,
    &inoutEdit2,
    nsString("chrome://global/skin/question-icon.gif").GetUnicode() ,
    &inoutCheckbox,
    4, /* number of buttons */
    2, /* number of edit fields */
    0, /* is first edit field a password field */
    &buttonPressed);
#endif

}

PRIVATE nsresult cookie_ProfileDirectory(nsFileSpec& dirSpec) {
  nsIFileSpec* spec = 
    NS_LocateFileOrDirectory(nsSpecialFileSpec::App_UserProfileDirectory50);
  if (!spec) {
    return NS_ERROR_FAILURE;
  }
  nsresult res = spec->GetFileSpec(&dirSpec);
  NS_RELEASE(spec);
  return res;
}

/*
 * Write a line to a file
 * return NS_OK if no error occurs
 */
nsresult
cookie_Put(nsOutputFileStream strm, const nsString& aLine)
{
  /* allocate a buffer from the heap */
  char * cp = aLine.ToNewCString();
  if (! cp) {
    return NS_ERROR_FAILURE;
  }

  /* output entire line */
  strm.write(cp, aLine.Length());
  nsCRT::free(cp);
  return NS_OK;
}

/*
 * Write an integer to a file
 * return NS_OK if no error occurs
 */
nsresult
cookie_PutInt(nsOutputFileStream strm, PRInt32 i)
{
  char c[2];
  c[1] = '\0';
  if (i == 0) {
    return NS_OK;
  }
  cookie_PutInt (strm, i/10);
  c[0] = '0' + i%10;
  nsString tempStr; tempStr.AssignWithConversion(c);
  return cookie_Put(strm, tempStr);
}

nsresult cookie_Get(nsInputFileStream strm, char& c) {
#define BUFSIZE 128
  static char buffer[BUFSIZE];
  static PRInt32 next = BUFSIZE, count = BUFSIZE;

  if (next == count) {
    if (BUFSIZE > count) { // never say "count < ..." vc6.0 thinks this is a template beginning and crashes
      next = BUFSIZE;
      count = BUFSIZE;
      return NS_ERROR_FAILURE;
    }
    count = strm.read(buffer, BUFSIZE);
    next = 0;
    if (count == 0) {
      next = BUFSIZE;
      count = BUFSIZE;
      return NS_ERROR_FAILURE;
    }
  }
  c = buffer[next++];
  return NS_OK;
}

/*
 * get a line from a file
 * return -1 if end of file reached
 * strip carriage returns and line feeds from end of line
 */
PRInt32
cookie_GetLine(nsInputFileStream strm, nsAutoString& aLine) {

  /* read the line */
  aLine.Truncate();
  char c;
  for (;;) {
    if NS_FAILED(cookie_Get(strm, c)) {
      return -1;
    }
    if (c == '\n') {
      break;
    }

    if (c != '\r') {
      aLine.AppendWithConversion(c);
    }
  }
  return 0;
}


PRIVATE void
cookie_LockList(void) {
  if(!cookie_cookieLockMonitor) {
    cookie_cookieLockMonitor = PR_NewNamedMonitor("cookie-lock");
  }
  PR_EnterMonitor(cookie_cookieLockMonitor);
  while(PR_TRUE) {

    /* no current owner or owned by this thread */
    PRThread * t = PR_CurrentThread();
    if(cookie_cookieLockOwner == NULL || cookie_cookieLockOwner == t) {
      cookie_cookieLockOwner = t;
      cookie_cookieLockCount++;
      PR_ExitMonitor(cookie_cookieLockMonitor);
      return;
    }

    /* owned by someone else -- wait till we can get it */
    PR_Wait(cookie_cookieLockMonitor, PR_INTERVAL_NO_TIMEOUT);
  }
}

PRIVATE void
cookie_UnlockList(void) {
   PR_EnterMonitor(cookie_cookieLockMonitor);

#ifdef DEBUG
  /* make sure someone doesn't try to free a lock they don't own */
  PR_ASSERT(cookie_cookieLockOwner == PR_CurrentThread());
#endif

  cookie_cookieLockCount--;
  if(cookie_cookieLockCount == 0) {
    cookie_cookieLockOwner = NULL;
    PR_Notify(cookie_cookieLockMonitor);
  }
  PR_ExitMonitor(cookie_cookieLockMonitor);
}

PRIVATE void
cookie_LockDeferList(void) {
  if(!cookie_deferLockMonitor) {
    cookie_deferLockMonitor = PR_NewNamedMonitor("defer_cookie-lock");
  }
  PR_EnterMonitor(cookie_deferLockMonitor);
  while(PR_TRUE) {

    /* no current owner or owned by this thread */
    PRThread * t = PR_CurrentThread();
    if(cookie_deferLockOwner == NULL || cookie_deferLockOwner == t) {
      cookie_deferLockOwner = t;
      cookie_deferLockCount++;
      PR_ExitMonitor(cookie_deferLockMonitor);
      return;
    }

    /* owned by someone else -- wait till we can get it */
    PR_Wait(cookie_deferLockMonitor, PR_INTERVAL_NO_TIMEOUT);
  }
}

PRIVATE void
cookie_UnlockDeferList(void) {
  PR_EnterMonitor(cookie_deferLockMonitor);

#ifdef DEBUG
  /* make sure someone doesn't try to free a lock they don't own */
  PR_ASSERT(cookie_deferLockOwner == PR_CurrentThread());
#endif

  cookie_deferLockCount--;
  if(cookie_deferLockCount == 0) {
    cookie_deferLockOwner = NULL;
    PR_Notify(cookie_deferLockMonitor);
  }
  PR_ExitMonitor(cookie_deferLockMonitor);
}

static PRMonitor * cookie_permission_lock_monitor = NULL;
static PRThread  * cookie_permission_lock_owner = NULL;
static int cookie_permission_lock_count = 0;

PRIVATE void
permission_LockList(void) {
  if(!cookie_permission_lock_monitor)
  cookie_permission_lock_monitor = PR_NewNamedMonitor("cookie_permission-lock");
  PR_EnterMonitor(cookie_permission_lock_monitor);
  while(PR_TRUE) {

    /* no current owner or owned by this thread */
    PRThread * t = PR_CurrentThread();
    if(cookie_permission_lock_owner == NULL || cookie_permission_lock_owner == t) {
      cookie_permission_lock_owner = t;
      cookie_permission_lock_count++;
      PR_ExitMonitor(cookie_permission_lock_monitor);
      return;
    }

    /* owned by someone else -- wait till we can get it */
    PR_Wait(cookie_permission_lock_monitor, PR_INTERVAL_NO_TIMEOUT);
  }
}

PRIVATE void
permission_UnlockList(void) {
  PR_EnterMonitor(cookie_permission_lock_monitor);

#ifdef DEBUG
  /* make sure someone doesn't try to free a lock they don't own */
  PR_ASSERT(cookie_permission_lock_owner == PR_CurrentThread());
#endif

  cookie_permission_lock_count--;
  if(cookie_permission_lock_count == 0) {
    cookie_permission_lock_owner = NULL;
    PR_Notify(cookie_permission_lock_monitor);
  }
  PR_ExitMonitor(cookie_permission_lock_monitor);
}

PRIVATE void
permission_Save();
PRIVATE void
cookie_Save();

PRIVATE void
permission_Free(PRInt32 hostNumber, PRInt32 type, PRBool save) {
  /*
   * This routine should only be called while holding the
   * cookie permission list lock
   */

  /* get the indicated host in the list */
  if (cookie_permissionList == nsnull) {
    return;
  }

  permission_HostStruct * hostStruct;
  permission_TypeStruct * typeStruct;
  PRInt32 actualHostNumber = -1;
  PRInt32 typeIndex;

  PRInt32 count = cookie_permissionList->Count();
  for (PRInt32 j=0, k=0; j<count; j++) {
    hostStruct = NS_STATIC_CAST
      (permission_HostStruct*, cookie_permissionList->ElementAt(j));
    if (!hostStruct) {
      return;
    }
    PRBool present = PR_FALSE;
    PRInt32 count2 = hostStruct->permissionList->Count();
    for (typeIndex=0; typeIndex<count2; typeIndex++) {
      typeStruct = NS_STATIC_CAST
        (permission_TypeStruct*, hostStruct->permissionList->ElementAt(typeIndex));
      if (typeStruct->type == type) {
        present = PR_TRUE;
        break;
      }
    }
    if (present) {
      if (k == hostNumber) {
        actualHostNumber = j;
        break;
      }
      k++;
    }
  }

  if (actualHostNumber == -1) {
    /* host not found, something is wrong */
    return;
  }

  /* remove the particular type */
  hostStruct->permissionList->RemoveElementAt(typeIndex);

  /* if no types are present, remove the entry */
  PRInt32 count2 = hostStruct->permissionList->Count();
  if (count2 == 0) {
    PR_FREEIF(hostStruct->permissionList);
    cookie_permissionList->RemoveElementAt(actualHostNumber);
    PR_FREEIF(hostStruct->host);
    PR_Free(hostStruct);
  }

  /* write the changes out to disk */
  if (save) {
    cookie_permissionsChanged = PR_TRUE;
    permission_Save();
  }
}

/* blows away all permissions currently in the list */
PRIVATE void
cookie_RemoveAllPermissions() {
  permission_HostStruct * hostStruct;
  permission_TypeStruct * typeStruct;
 
  /* check for NULL or empty list */
  permission_LockList();
  if (cookie_permissionList == nsnull) {
    permission_UnlockList();
    return;
  }
  PRInt32 count = cookie_permissionList->Count();
  for (PRInt32 i = count-1; i >=0; i--) {
    hostStruct = NS_STATIC_CAST
      (permission_HostStruct*, cookie_permissionList->ElementAt(i));
    if (!hostStruct) {
      return;
    }
    PRInt32 count2 = hostStruct->permissionList->Count();
    for (PRInt32 typeIndex = count2-1; typeIndex >=0; typeIndex--) {
      typeStruct = NS_STATIC_CAST
        (permission_TypeStruct*, hostStruct->permissionList->ElementAt(typeIndex));
      permission_Free(i, typeStruct->type, PR_FALSE);
    }
  }
  delete cookie_permissionList;
  cookie_permissionList = NULL;
  permission_UnlockList();
}

/* This should only get called while holding the cookie-lock */
PRIVATE void
cookie_FreeCookie(cookie_CookieStruct * cookie) {
  if(!cookie) {
    return;
  }
  if (cookie_cookieList == nsnull) {
    return;
  }
  cookie_cookieList->RemoveElement(cookie);
  PR_FREEIF(cookie->path);
  PR_FREEIF(cookie->host);
  PR_FREEIF(cookie->name);
  PR_FREEIF(cookie->cookie);
  PR_Free(cookie);
  cookie_cookiesChanged = PR_TRUE;
}

/* blows away all cookies currently in the list, then blows away the list itself
 * nulling it after it's free'd
 */
PRIVATE void
cookie_RemoveAllCookies() {
  cookie_CookieStruct * victim;

  /* check for NULL or empty list */
  cookie_LockList();
  if (cookie_cookieList == nsnull) {
    cookie_UnlockList();
    return;
  }
  PRInt32 count = cookie_cookieList->Count();
  for (PRInt32 i = count-1; i >=0; i--) {
    victim = NS_STATIC_CAST(cookie_CookieStruct*, cookie_cookieList->ElementAt(i));
    if (victim) {
      cookie_FreeCookie(victim);
    }
  }
  delete cookie_cookieList;
  cookie_cookieList = NULL;
  cookie_UnlockList();
}

PUBLIC void
COOKIE_RemoveAllCookies()
{
  cookie_RemoveAllPermissions();
  cookie_RemoveAllCookies();
}

PRIVATE void
cookie_RemoveOldestCookie(void) {
  cookie_CookieStruct * cookie_s;
  cookie_CookieStruct * oldest_cookie;
  cookie_LockList();

  if (cookie_cookieList == nsnull) {
    cookie_UnlockList();
    return;
  }
   
  PRInt32 count = cookie_cookieList->Count();
  if (count == 0) {
    cookie_UnlockList();
    return;
  }
  oldest_cookie = NS_STATIC_CAST(cookie_CookieStruct*, cookie_cookieList->ElementAt(0));
  for (PRInt32 i = 1; i < count; ++i) {
    cookie_s = NS_STATIC_CAST(cookie_CookieStruct*, cookie_cookieList->ElementAt(i));
    if (cookie_s) {
      if(cookie_s->lastAccessed < oldest_cookie->lastAccessed) {
        oldest_cookie = cookie_s;
      }
    }
  }
  if(oldest_cookie) {
    // TRACEMSG(("Freeing cookie because global max cookies has been exceeded"));
    cookie_FreeCookie(oldest_cookie);
  }

  cookie_UnlockList();
}

/* Remove any expired cookies from memory 
** This routine should only be called while holding the cookie list lock
*/
PRIVATE void
cookie_RemoveExpiredCookies() {
  cookie_CookieStruct * cookie_s;
  time_t cur_time = get_current_time();
  
  if (cookie_cookieList == nsnull) {
    return;
  }
  
  PRInt32 count = cookie_cookieList->Count();
  for (PRInt32 i = 0; i < count; ++i) {
    cookie_s = NS_STATIC_CAST(cookie_CookieStruct*, cookie_cookieList->ElementAt(i));
    if (cookie_s) {
     /*
       * Don't get rid of expire time 0 because these need to last for 
       * the entire session. They'll get cleared on exit.
       */
      if( cookie_s->expires && (cookie_s->expires < cur_time) ) {
        cookie_FreeCookie(cookie_s);
        /*
         * Reset the list_ptr to the beginning of the list.
         * Do this because list_ptr's object was just freed
         * by the call to cookie_FreeCookie struct, even
         * though it's inefficient.
         */
      }
    }
  }
}

/* checks to see if the maximum number of cookies per host
 * is being exceeded and deletes the oldest one in that
 * case
 * This routine should only be called while holding the cookie lock
 */
PRIVATE void
cookie_CheckForMaxCookiesFromHo(const char * cur_host) {
  cookie_CookieStruct * cookie_s;
  cookie_CookieStruct * oldest_cookie = 0;
  int cookie_count = 0;
  
  if (cookie_cookieList == nsnull) {
    return;
  }
  
  PRInt32 count = cookie_cookieList->Count();
  for (PRInt32 i = 0; i < count; ++i) {
    cookie_s = NS_STATIC_CAST(cookie_CookieStruct*, cookie_cookieList->ElementAt(i));
    if (cookie_s) {
      if(!PL_strcasecmp(cookie_s->host, cur_host)) {
        cookie_count++;
        if(!oldest_cookie || oldest_cookie->lastAccessed > cookie_s->lastAccessed) {
          oldest_cookie = cookie_s;
        }
      }
    }
  }
  if(cookie_count >= MAX_COOKIES_PER_SERVER && oldest_cookie) {
    // TRACEMSG(("Freeing cookie because max cookies per server has been exceeded"));
    cookie_FreeCookie(oldest_cookie);
  }
}


/* search for previous exact match
** This routine should only be called while holding the cookie lock
*/
PRIVATE cookie_CookieStruct *
cookie_CheckForPrevCookie(char * path, char * hostname, char * name) {
  cookie_CookieStruct * cookie_s;
  if (cookie_cookieList == nsnull) {
    return NULL;
  }
  
  PRInt32 count = cookie_cookieList->Count();
  for (PRInt32 i = 0; i < count; ++i) {
    cookie_s = NS_STATIC_CAST(cookie_CookieStruct*, cookie_cookieList->ElementAt(i));
    if (cookie_s) {
      if(path && hostname && cookie_s->path && cookie_s->host && cookie_s->name
        && !PL_strcmp(name, cookie_s->name) && !PL_strcmp(path, cookie_s->path)
        && !PL_strcasecmp(hostname, cookie_s->host)) {
        return(cookie_s);
      }
    }
  }
  return(NULL);
}

/* cookie utility functions */
PRIVATE void
cookie_SetBehaviorPref(COOKIE_BehaviorEnum x) {
  cookie_behavior = x;
}

PRIVATE void
cookie_SetWarningPref(PRBool x) {
  cookie_warning = x;
}

PRIVATE void
cookie_SetLifetimePref(COOKIE_LifetimeEnum x) {
  cookie_lifetimeOpt = x;
}

PRIVATE void
cookie_SetLifetimeLimit(PRInt32 x) {
  // save limit as seconds instead of days
  cookie_lifetimeLimit = x*24*60*60;
}

PUBLIC COOKIE_BehaviorEnum
COOKIE_GetBehaviorPref() {
  return cookie_behavior;
}

PRIVATE PRBool
cookie_GetWarningPref() {
  return cookie_warning;
}

PRIVATE COOKIE_LifetimeEnum
cookie_GetLifetimePref() {
  return cookie_lifetimeOpt;
}

PRIVATE time_t
cookie_GetLifetimeTime() {
  // return time after which lifetime is excessive
  return get_current_time() + cookie_lifetimeLimit;
}

PRIVATE PRBool
cookie_GetLifetimeAsk(time_t expireTime) {
  // return true if we should ask about this cookie
  return (cookie_GetLifetimePref() == COOKIE_Ask)
    && (cookie_GetLifetimeTime() < expireTime);
}

PRIVATE time_t
cookie_TrimLifetime(time_t expires) {
  // return potentially-trimmed lifetime
  if (cookie_GetLifetimePref() == COOKIE_Trim) {
    // a limit of zero means expire cookies at end of session
    if (cookie_lifetimeLimit == 0) {
      return 0;
    }
    time_t limit = cookie_GetLifetimeTime();
    if ((unsigned)expires > (unsigned)limit) {
      return limit;
    }
  }
  return expires;
}

MODULE_PRIVATE int PR_CALLBACK
cookie_BehaviorPrefChanged(const char * newpref, void * data) {
  PRInt32 n;
  nsresult rv;
  NS_WITH_SERVICE(nsIPref, prefs, "component://netscape/preferences", &rv);
  if (NS_FAILED(prefs->GetIntPref(cookie_behaviorPref, &n))) {
    n = COOKIE_Accept;
  }
  cookie_SetBehaviorPref((COOKIE_BehaviorEnum)n);
  return 0;
}

MODULE_PRIVATE int PR_CALLBACK
cookie_WarningPrefChanged(const char * newpref, void * data) {
  PRBool x;
  nsresult rv;
  NS_WITH_SERVICE(nsIPref, prefs, "component://netscape/preferences", &rv);
  if (NS_FAILED(prefs->GetBoolPref(cookie_warningPref, &x))) {
    x = PR_FALSE;
  }
  cookie_SetWarningPref(x);
  return 0;
}

/* cookie utility functions */
PRIVATE void
image_SetBehaviorPref(COOKIE_BehaviorEnum x) {
  image_behavior = x;
}

PRIVATE void
image_SetWarningPref(PRBool x) {
  image_warning = x;
}

PRIVATE COOKIE_BehaviorEnum
image_GetBehaviorPref() {
  return image_behavior;
}

PRIVATE PRBool
image_GetWarningPref() {
  return image_warning;
}

MODULE_PRIVATE int PR_CALLBACK
image_BehaviorPrefChanged(const char * newpref, void * data) {
  PRInt32 n;
  nsresult rv;
  NS_WITH_SERVICE(nsIPref, prefs, "component://netscape/preferences", &rv);
  if (NS_FAILED(prefs->GetIntPref(image_behaviorPref, &n))) {
    image_SetBehaviorPref(COOKIE_Accept);
  } else {
    image_SetBehaviorPref((COOKIE_BehaviorEnum)n);
  }
  return 0;
}

MODULE_PRIVATE int PR_CALLBACK
image_WarningPrefChanged(const char * newpref, void * data) {
  PRBool x;
  nsresult rv;
  NS_WITH_SERVICE(nsIPref, prefs, "component://netscape/preferences", &rv);
  if (NS_FAILED(prefs->GetBoolPref(image_warningPref, &x))) {
    x = PR_FALSE;
  }
  image_SetWarningPref(x);
  return 0;
}

MODULE_PRIVATE int PR_CALLBACK
cookie_LifetimeOptPrefChanged(const char * newpref, void * data) {
  PRInt32 n;
  nsresult rv;
  NS_WITH_SERVICE(nsIPref, prefs, "component://netscape/preferences", &rv);
  if (NS_FAILED(prefs->GetIntPref(cookie_lifetimePref, &n))) {
    n = COOKIE_Normal;
  }
  cookie_SetLifetimePref((COOKIE_LifetimeEnum)n);
  return 0;
}

MODULE_PRIVATE int PR_CALLBACK
cookie_LifetimeLimitPrefChanged(const char * newpref, void * data) {
  PRInt32 n;
  nsresult rv;
  NS_WITH_SERVICE(nsIPref, prefs, "component://netscape/preferences", &rv);
  if (!NS_FAILED(prefs->GetIntPref(cookie_lifetimeValue, &n))) {
    cookie_SetLifetimeLimit(n);
  }
  return 0;
}

/*
 * search if permission already exists
 */
nsresult
permission_CheckFromList(char * hostname, PRBool &permission, PRInt32 type) {
  permission_HostStruct * hostStruct;
  permission_TypeStruct * typeStruct;

  /* ignore leading period in host name */
  while (hostname && (*hostname == '.')) {
    hostname++;
  }

  permission_LockList();

  /* return if cookie_permission list does not exist */
  if (cookie_permissionList == nsnull) {
    permission_UnlockList();
    return NS_ERROR_FAILURE;
  }

  /* find host name within list */
  PRInt32 count = cookie_permissionList->Count();
  for (PRInt32 i = 0; i < count; ++i) {
    hostStruct = NS_STATIC_CAST(permission_HostStruct*, cookie_permissionList->ElementAt(i));
    if (hostStruct) {
      if(hostname && hostStruct->host && !PL_strcasecmp(hostname, hostStruct->host)) {

        /* search for type in the permission list for this host */
        PRInt32 count2 = hostStruct->permissionList->Count();
        for (PRInt32 typeIndex=0; typeIndex<count2; typeIndex++) {
          typeStruct = NS_STATIC_CAST
            (permission_TypeStruct*, hostStruct->permissionList->ElementAt(typeIndex));
          if (typeStruct->type == type) {

            /* type found.  Obtain the corresponding permission */
            permission = typeStruct->permission;
            permission_UnlockList();
            return NS_OK;
          }
        }

        /* type not found, return failure */
        permission_UnlockList();
        return NS_ERROR_FAILURE;
      }
    }
  }

  /* not found, return failure */
  permission_UnlockList();
  return NS_ERROR_FAILURE;
}

PRBool
permission_GetRememberChecked(PRInt32 type) {
  if (type == COOKIEPERMISSION) {
    return cookie_rememberChecked;
  } else if (type == IMAGEPERMISSION) {
    return image_rememberChecked;
  } else {
    return PR_FALSE;
  }
}

void
permission_SetRememberChecked(PRInt32 type, PRBool value) {
  if (type == COOKIEPERMISSION) {
    cookie_rememberChecked = value;
  } else if (type == IMAGEPERMISSION) {
    image_rememberChecked = value;
  }
}

nsresult
permission_Add(char * host, PRBool permission, PRInt32 type, PRBool save);

PRBool
permission_Check(
     nsIPrompt *aPrompter,
     char * hostname,
     PRInt32 type,
     PRBool warningPref,
     PRUnichar * message)
{
  PRBool permission;

  /* try to make decision based on saved permissions */
  if (NS_SUCCEEDED(permission_CheckFromList(hostname, permission, type))) {
    return permission;
  }

  /* see if we need to prompt */
  if(!warningPref) {
    return PR_TRUE;
  }

  /* we need to prompt */
  PRBool rememberChecked = permission_GetRememberChecked(type);
  PRUnichar * remember_string = cookie_Localize("RememberThisDecision");
  permission = cookie_CheckConfirmYN(aPrompter, message, remember_string, &rememberChecked);

  /* see if we need to remember this decision */
  if (rememberChecked) {
    char * hostname2 = NULL;
    permission_LockList();
    /* ignore leading periods in host name */
    while (hostname && (*hostname == '.')) {
      hostname++;
    }
    StrAllocCopy(hostname2, hostname);
    permission_Add(hostname2, permission, type, PR_TRUE);
    permission_UnlockList();
  }
  if (rememberChecked != permission_GetRememberChecked(type)) {
    permission_SetRememberChecked(type, rememberChecked);
    cookie_permissionsChanged = PR_TRUE;
    permission_Save();
  }
  return permission;
}

PRIVATE int
cookie_SameDomain(char * currentHost, char * firstHost);

PUBLIC nsresult
Image_CheckForPermission(char * hostname, char * firstHostname, PRBool &permission) {

  /* exit if imageblocker is not enabled */
  nsresult rv;
  PRBool prefvalue = PR_FALSE;
  NS_WITH_SERVICE(nsIPref, prefs, "component://netscape/preferences", &rv);
  if (NS_FAILED(rv) || 
      NS_FAILED(prefs->GetBoolPref("imageblocker.enabled", &prefvalue)) ||
      !prefvalue) {
    permission = PR_TRUE;
    return NS_OK;
  }

  /* try to make a decision based on pref settings */
  if ((image_GetBehaviorPref() == COOKIE_DontUse)) {
    permission = PR_FALSE;
    return NS_OK;
  }
  if (image_GetBehaviorPref() == COOKIE_DontAcceptForeign) {
    /* compare tails of names checking to see if they have a common domain */
    /* we do this by comparing the tails of both names where each tail includes at least one dot */
    PRInt32 dotcount = 0;
    char * tailHostname = hostname + PL_strlen(hostname) - 1;
    while (tailHostname > hostname) { 
      if (*tailHostname == '.') {
        dotcount++;
      }
      if (dotcount == 2) {
        tailHostname++;
        break;
      }
      tailHostname--;
    }
    dotcount = 0;
    char * tailFirstHostname = firstHostname + PL_strlen(firstHostname) - 1;
    while (tailFirstHostname > firstHostname) { 
      if (*tailFirstHostname == '.') {
        dotcount++;
      }
      if (dotcount == 2) {
        tailFirstHostname++;
        break;
      }
      tailFirstHostname--;
    }
    if (PL_strcmp(tailFirstHostname, tailHostname)) {
      permission = PR_FALSE;
      return NS_OK;
    }
  }

  /* use common routine to make decision */
  PRUnichar * message = cookie_Localize("PermissionToAcceptImage");
  PRUnichar * new_string = nsTextFormatter::smprintf(message, hostname ? hostname : "");
  permission = permission_Check(0, hostname, IMAGEPERMISSION,
                                image_GetWarningPref(), new_string);
  PR_FREEIF(new_string);
  Recycle(message);
  return NS_OK;
}

/* called from mkgeturl.c, NET_InitNetLib(). This sets the module local 
 * cookie pref variables and registers the callbacks
 */
PUBLIC void
COOKIE_RegisterCookiePrefCallbacks(void) {
  PRInt32 n;
  PRBool x;
  nsresult rv;
  NS_WITH_SERVICE(nsIPref, prefs, "component://netscape/preferences", &rv);

  // Initialize for cookie_behaviorPref
  if (NS_FAILED(prefs->GetIntPref(cookie_behaviorPref, &n))) {
    n = COOKIE_Accept;
  }
  cookie_SetBehaviorPref((COOKIE_BehaviorEnum)n);
  prefs->RegisterCallback(cookie_behaviorPref, cookie_BehaviorPrefChanged, NULL);

  // Initialize for cookie_warningPref
  if (NS_FAILED(prefs->GetBoolPref(cookie_warningPref, &x))) {
    x = PR_FALSE;
  }
  cookie_SetWarningPref(x);
  prefs->RegisterCallback(cookie_warningPref, cookie_WarningPrefChanged, NULL);

  if (NS_FAILED(prefs->GetIntPref(image_behaviorPref, &n))) {
    n = COOKIE_Accept;
  }
  image_SetBehaviorPref((COOKIE_BehaviorEnum)n);
  prefs->RegisterCallback(image_behaviorPref, image_BehaviorPrefChanged, NULL);

  if (NS_FAILED(prefs->GetBoolPref(image_warningPref, &x))) {
    x = PR_FALSE;
  }
  image_SetWarningPref(x);
  prefs->RegisterCallback(image_warningPref, image_WarningPrefChanged, NULL);

  // Initialize for cookie_lifetimePref
  if (NS_FAILED(prefs->GetIntPref(cookie_lifetimePref, &n))) {
    n = COOKIE_Normal;
  }
  cookie_SetLifetimePref((COOKIE_LifetimeEnum)n);
  prefs->RegisterCallback(cookie_lifetimePref, cookie_LifetimeOptPrefChanged, NULL);

  // Initialize for cookie_lifetimeValue
  if (!NS_FAILED(prefs->GetIntPref(cookie_lifetimeValue, &n))) {
    cookie_SetLifetimeLimit(n);
  }
  prefs->RegisterCallback(cookie_lifetimeValue, cookie_LifetimeLimitPrefChanged, NULL);
}

PRBool
cookie_IsInDomain(char* domain, char* host, int hostLength) {
  int domainLength = PL_strlen(domain);

  /*
   * special case for domainName = .hostName
   *    e.g., hostName = netscape.com
   *          domainName = .netscape.com
   */
  if ((domainLength == hostLength+1) && (domain[0] == '.') && 
      !PL_strncasecmp(&domain[1], host, hostLength)) {
    return PR_TRUE;
  }

  /*
   * normal case for hostName = x<domainName>
   *    e.g., hostName = home.netscape.com
   *          domainName = .netscape.com
   */
  if(domainLength <= hostLength &&
      !PL_strncasecmp(domain, &host[hostLength-domainLength], domainLength)) {
    return PR_TRUE;
  }

  /* tests failed, not in domain */
  return PR_FALSE;
}

/* returns PR_TRUE if authorization is required
** 
**
** IMPORTANT:  Now that this routine is multi-threaded it is up
**             to the caller to free any returned string
*/
PUBLIC char *
COOKIE_GetCookie(char * address) {
  char *name=0;
  cookie_CookieStruct * cookie_s;
  PRBool first=PR_TRUE;
  PRBool xxx = PR_FALSE;
  time_t cur_time = get_current_time();

  int host_length;

  /* return string to build */
  char * rv=0;

  /* disable cookies if the user's prefs say so */
  if(COOKIE_GetBehaviorPref() == COOKIE_DontUse) {
    return NULL;
  }
  if (!PL_strncasecmp(address, "https", 5)) {
     xxx = PR_TRUE;
  }

  /* search for all cookies */
  cookie_LockList();
  if (cookie_cookieList == nsnull) {
    cookie_UnlockList();
    return NULL;
  }
  char *host = cookie_ParseURL(address, GET_HOST_PART);
  char *path = cookie_ParseURL(address, GET_PATH_PART);
  PRInt32 count = cookie_cookieList->Count();
  for (PRInt32 i = 0; i < count; ++i) {
    cookie_s = NS_STATIC_CAST(cookie_CookieStruct*, cookie_cookieList->ElementAt(i));
    if (cookie_s == nsnull) {
      continue;
    }
    if(!cookie_s->host) {
      continue;
    }

    /* check the host or domain first */
    if(cookie_s->isDomain) {
      char *cp;

      /* calculate the host length by looking at all characters up to a
       * colon or '\0'.  That way we won't include port numbers in domains
       */
      for(cp=host; *cp != '\0' && *cp != ':'; cp++) {
        ; /* null body */ 
      }
      host_length = cp - host;
      if(!cookie_IsInDomain(cookie_s->host, host, host_length)) {
        continue;
      }
    } else if(PL_strcasecmp(host, cookie_s->host)) {
      /* hostname matchup failed. FAIL */
      continue;
    }

    /* shorter strings always come last so there can be no ambiquity */
    if(cookie_s->path && !PL_strncmp(path, cookie_s->path, PL_strlen(cookie_s->path))) {

      /* if the cookie is xxx and the path isn't, dont send it */
      if (cookie_s->xxx & !xxx) {
        continue; /* back to top of while */
      }

      /* check for expired cookies */
      if( cookie_s->expires && (cookie_s->expires < cur_time) ) {
        /* expire and remove the cookie */
        cookie_FreeCookie(cookie_s);

        /* start the list parsing over :( we must also start the string over */
        PR_FREEIF(rv);
        rv = NULL;
        i = -1;
        first = PR_TRUE; /* reset first */
        continue;
      }
      if(first) {
        first = PR_FALSE;
      } else {
        StrAllocCat(rv, "; ");
      }
      if(cookie_s->name && *cookie_s->name != '\0') {
        cookie_s->lastAccessed = cur_time;
        StrAllocCopy(name, cookie_s->name);
        StrAllocCat(name, "=");

#ifdef PREVENT_DUPLICATE_NAMES
        /* make sure we don't have a previous name mapping already in the string */
        if(!rv || !PL_strstr(rv, name)) {
          StrAllocCat(rv, name);
          StrAllocCat(rv, cookie_s->cookie);
        }
#else
        StrAllocCat(rv, name);
        StrAllocCat(rv, cookie_s->cookie);
#endif /* PREVENT_DUPLICATE_NAMES */
      } else {
        StrAllocCat(rv, cookie_s->cookie);
      }
    }
  }

  cookie_UnlockList();
  PR_FREEIF(name);
  PR_FREEIF(path);
  PR_FREEIF(host);

  /* may be NULL */
  return(rv);
}

/* Determines whether the inlineHost is in the same domain as the currentHost.
 * For use with rfc 2109 compliance/non-compliance.
 */
PRIVATE int
cookie_SameDomain(char * currentHost, char * firstHost) {
  char * dot = 0;
  char * currentDomain = 0;
  char * firstDomain = 0;
  if(!currentHost || !firstHost) {
    return 0;
  }

  /* case insensitive compare */
  if(PL_strcasecmp(currentHost, firstHost) == 0) {
    return 1;
  }
  currentDomain = PL_strchr(currentHost, '.');
  firstDomain = PL_strchr(firstHost, '.');
  if(!currentDomain || !firstDomain) {
    return 0;
  }

  /* check for at least two dots before continuing, if there are
   * not two dots we don't have enough information to determine
   * whether or not the firstDomain is within the currentDomain
   */
  dot = PL_strchr(firstDomain, '.');
  if(dot) {
    dot = PL_strchr(dot+1, '.');
  } else {
    return 0;
  }

  /* handle .com. case */
  if(!dot || (*(dot+1) == '\0')) {
    return 0;
  }
  if(!PL_strcasecmp(firstDomain, currentDomain)) {
    return 1;
  }
  return 0;
}

PRBool
cookie_isForeign (char * curURL, char * firstURL) {
  char * curHost = cookie_ParseURL(curURL, GET_HOST_PART);
  char * firstHost = cookie_ParseURL(firstURL, GET_HOST_PART);
  char * curHostColon = 0;
  char * firstHostColon = 0;

  /* strip ports */
  curHostColon = PL_strchr(curHost, ':');
  if(curHostColon) {
    *curHostColon = '\0';
  }
  firstHostColon = PL_strchr(firstHost, ':');
  if(firstHostColon) {
    *firstHostColon = '\0';
  }

  /* determine if it's foreign */
  PRBool retval = (!cookie_SameDomain(curHost, firstHost));

  /* clean up our garbage and return */
  if(curHostColon) {
    *curHostColon = ':';
  }
  if(firstHostColon) {
    *firstHostColon = ':';
  }
  PR_FREEIF(curHost);
  PR_FREEIF(firstHost);
  return retval;
}

/* returns PR_TRUE if authorization is required
** 
**
** IMPORTANT:  Now that this routine is multi-threaded it is up
**             to the caller to free any returned string
*/
PUBLIC char *
COOKIE_GetCookieFromHttp(char * address, char * firstAddress) {

  if ((COOKIE_GetBehaviorPref() == COOKIE_DontAcceptForeign) &&
      cookie_isForeign(address, firstAddress)) {

    /*
     * WARNING!!! This is a different behavior than 4.x.  In 4.x we used this pref to
     * control the setting of cookies only.  Here we are also blocking the getting of
     * cookies if the pref is set.  It may be that we need a separate pref to block the
     * getting of cookies.  But for now we are putting both under one pref since that
     * is cleaner.  If it turns out that this breaks some important websites, we may
     * have to resort to two prefs
     */

    return NULL;
  }
  return COOKIE_GetCookie(address);
}

nsresult
permission_Add(char * host, PRBool permission, PRInt32 type, PRBool save) {
  /*
   * This routine should only be called while holding the
   * cookie permission list lock
   */

  /* create permission list if it does not yet exist */
  if(!cookie_permissionList) {
    cookie_permissionList = new nsVoidArray();
    if(!cookie_permissionList) {
      Recycle(host);
      return NS_ERROR_FAILURE;
    }
  }

  /* find existing entry for host */
  permission_HostStruct * hostStruct;
  PRBool HostFound = PR_FALSE;
  PRInt32 count = cookie_permissionList->Count();
  PRInt32 i;
  for (i = 0; i < count; ++i) {
    hostStruct = NS_STATIC_CAST(permission_HostStruct*, cookie_permissionList->ElementAt(i));
    if (hostStruct) {
      if (PL_strcasecmp(host,hostStruct->host)==0) {

        /* host found in list */
        Recycle(host);
        HostFound = PR_TRUE;
        break;
#ifdef alphabetize
      } else if (PL_strcasecmp(host,hostStruct->host)<0) {

        /* need to insert new entry here */
        break;
#endif
      }
    }
  }

  if (!HostFound) {

    /* create a host structure for the host */
    hostStruct = PR_NEW(permission_HostStruct);
    if (!hostStruct) {
      Recycle(host);
      return NS_ERROR_FAILURE;
    }
    hostStruct->host = host;
    hostStruct->permissionList = new nsVoidArray();
    if(!hostStruct->permissionList) {
      PR_Free(hostStruct);
      Recycle(host);
      return NS_ERROR_FAILURE;
    }

    /* insert host structure into the list */
    if (i == cookie_permissionList->Count()) {
      cookie_permissionList->AppendElement(hostStruct);
    } else {
      cookie_permissionList->InsertElementAt(hostStruct, i);
    }
  }

  /* see if host already has an entry for this type */
  permission_TypeStruct * typeStruct;
  PRBool typeFound = PR_FALSE;
  PRInt32 count2 = hostStruct->permissionList->Count();
  for (PRInt32 typeIndex=0; typeIndex<count2; typeIndex++) {
    typeStruct = NS_STATIC_CAST
      (permission_TypeStruct*, hostStruct->permissionList->ElementAt(typeIndex));
    if (typeStruct->type == type) {

      /* type found.  Modify the corresponding permission */
      typeStruct->permission = permission;
      typeFound = PR_TRUE;
      break;
    }
  }

  /* create a type structure and attach it to the host structure */
  if (!typeFound) {
    typeStruct = PR_NEW(permission_TypeStruct);
    typeStruct->type = type;
    typeStruct->permission = permission;
    hostStruct->permissionList->AppendElement(typeStruct);
  }

  /* write the changes out to a file */
  if (save) {
    cookie_permissionsChanged = PR_TRUE;
    permission_Save();
  }
  return NS_OK;
}

MODULE_PRIVATE PRBool
cookie_IsFromHost(cookie_CookieStruct *cookie_s, char *host) {
  if (!cookie_s || !(cookie_s->host)) {
    return PR_FALSE;
  }
  if (cookie_s->isDomain) {
    char *cp;
    int host_length;

    /* calculate the host length by looking at all characters up to
     * a colon or '\0'.  That way we won't include port numbers
     * in domains
     */
    for(cp=host; *cp != '\0' && *cp != ':'; cp++) {
      ; /* null body */
    }
    host_length = cp - host;

    /* compare the tail end of host to cook_s->host */
    return cookie_IsInDomain(cookie_s->host, host, host_length);
  } else {
    return PL_strcasecmp(host, cookie_s->host) == 0;
  }
}

/* find out how many cookies this host has already set */
PRIVATE int
cookie_Count(char * host) {
  int count = 0;
  cookie_CookieStruct * cookie;
  cookie_LockList();
  if (cookie_cookieList == nsnull) {
    cookie_UnlockList();
    return count;
  }
  PRInt32 cookie_count = cookie_cookieList->Count();
  for (PRInt32 i = 0; i < cookie_count; ++i) {
    cookie = NS_STATIC_CAST(cookie_CookieStruct*, cookie_cookieList->ElementAt(i));
    if (cookie) {
      if (host && cookie_IsFromHost(cookie, host)) {
        count++;
      }
    }
  }
  cookie_UnlockList();
  return count;
}

PRIVATE void
cookie_Defer(char * curURL, char * setCookieHeader, time_t timeToExpire) {
  cookie_DeferStruct * defer_cookie = PR_NEW(cookie_DeferStruct);
  defer_cookie->curURL = NULL;
  StrAllocCopy(defer_cookie->curURL, curURL);
  defer_cookie->setCookieHeader = NULL;
  StrAllocCopy(defer_cookie->setCookieHeader, setCookieHeader);
  defer_cookie->timeToExpire = timeToExpire;
  cookie_LockDeferList();
  if (!cookie_deferList) {
    cookie_deferList = new nsVoidArray();
    if (!cookie_deferList) {
      PR_FREEIF(defer_cookie->curURL);
      PR_FREEIF(defer_cookie->setCookieHeader);
      PR_Free(defer_cookie);
      cookie_UnlockDeferList();
      return;
    }
  }
  cookie_deferList->InsertElementAt(defer_cookie, 0);
  cookie_UnlockDeferList();
}
           
PRIVATE void
cookie_SetCookieString(char * curURL, nsIPrompt *aPrompter, char * setCookieHeader, time_t timeToExpire );

PRIVATE void
cookie_Undefer() {
  cookie_DeferStruct * defer_cookie;
  cookie_LockDeferList();
  if(cookie_deferList == nsnull) {
    cookie_UnlockDeferList();
    return;
  }
  PRInt32 count = cookie_deferList->Count();
  if (count == 0) {
    cookie_UnlockDeferList();
    return;
  }

  defer_cookie = NS_STATIC_CAST(cookie_DeferStruct*, cookie_deferList->ElementAt(count-1));
  cookie_deferList->RemoveElementAt(count-1);
  cookie_UnlockDeferList();
  if (defer_cookie) {
    cookie_SetCookieString
      (defer_cookie->curURL, 0, defer_cookie->setCookieHeader, defer_cookie->timeToExpire);
    PR_FREEIF(defer_cookie->curURL);
    PR_FREEIF(defer_cookie->setCookieHeader);
    PR_Free(defer_cookie);
  }
}

/* Java script is calling COOKIE_SetCookieString, netlib is calling 
 * this via COOKIE_SetCookieStringFromHttp.
 */
PRIVATE void
cookie_SetCookieString(char * curURL, nsIPrompt *aPrompter, char * setCookieHeader, time_t timeToExpire) {
  cookie_CookieStruct * prev_cookie;
  char *path_from_header=NULL, *host_from_header=NULL;
  char *name_from_header=NULL, *cookie_from_header=NULL;
  char *cur_path = cookie_ParseURL(curURL, GET_PATH_PART);
  char *cur_host = cookie_ParseURL(curURL, GET_HOST_PART);
  char *semi_colon, *ptr, *equal;
  PRBool xxx=PR_FALSE, isDomain=PR_FALSE;
  PRBool bCookieAdded;
  PRBool pref_scd = PR_FALSE;

  /* Only allow cookies to be set in the listed contexts.
   * We don't want cookies being set in html mail. 
   */
/* We need to come back and work on this - Neeti 
  type = context->type;
  if(!((type==MWContextBrowser) || (type==MWContextHTMLHelp) || (type==MWContextPane))) {
    PR_Free(cur_path);
    PR_Free(cur_host);
    return;
  }
*/
  if(COOKIE_GetBehaviorPref() == COOKIE_DontUse) {
    PR_Free(cur_path);
    PR_Free(cur_host);
    return;
  }

//printf("\nSetCookieString(URL '%s', header '%s') time %d == %s\n",curURL,setCookieHeader,timeToExpire,asctime(gmtime(&timeToExpire)));
  if(cookie_GetLifetimePref() == COOKIE_Discard) {
    if(cookie_GetLifetimeTime() < timeToExpire) {
      PR_Free(cur_path);
      PR_Free(cur_host);
      return;
    }
  }

  /* Don't enter this routine if it is already in use by another
     thread.  Otherwise the "remember this decision" result of the
     other cookie (which came first) won't get applied to this cookie.
   */
  if (cookie_SetCookieStringInUse) {
    PR_Free(cur_path);
    PR_Free(cur_host);
    cookie_Defer(curURL, setCookieHeader, timeToExpire);
    return;
  }
  cookie_SetCookieStringInUse = PR_TRUE;
//HG87358 -- @@??

  /* terminate at any carriage return or linefeed */
  for(ptr=setCookieHeader; *ptr; ptr++) {
    if(*ptr == LF || *ptr == CR) {
      *ptr = '\0';
      break;
    }
  }

  /* parse path and expires attributes from header if present */
  semi_colon = PL_strchr(setCookieHeader, ';');
  if(semi_colon) {
    /* truncate at semi-colon and advance */
    *semi_colon++ = '\0';

    /* there must be some attributes. (hopefully) */
    if ((ptr=PL_strcasestr(semi_colon, "secure="))) {
      char cPre=*(ptr-1), cPost=*(ptr+6);
      if (((cPre==' ') || (cPre==';')) && (!cPost || (cPost==' ') || (cPost==';'))) {
        xxx = PR_TRUE;
      } 
    }

    /* look for the path attribute */
    ptr = PL_strcasestr(semi_colon, "path=");
    if(ptr) {
      /* allocate more than we need */
      StrAllocCopy(path_from_header, XP_StripLine(ptr+5));
      /* terminate at first space or semi-colon */
      for(ptr=path_from_header; *ptr != '\0'; ptr++) {
        if(COOKIE_IS_SPACE(*ptr) || *ptr == ';' || *ptr == ',') {
          *ptr = '\0';
          break;
        }
      }
    }

    /* look for the URI or URL attribute
     *
     * This might be a security hole so I'm removing
     * it for now.
     */

    /* look for a domain */
    ptr = PL_strcasestr(semi_colon, "domain=");
    if(ptr) {
      char *domain_from_header=NULL;
      char *dot, *colon;
      int domain_length, cur_host_length;

      /* allocate more than we need */
      StrAllocCopy(domain_from_header, XP_StripLine(ptr+7));

      /* terminate at first space or semi-colon */
      for(ptr=domain_from_header; *ptr != '\0'; ptr++) {
        if(COOKIE_IS_SPACE(*ptr) || *ptr == ';' || *ptr == ',') {
          *ptr = '\0';
          break;
        }
      }

      /* verify that this host has the authority to set for this domain.   We do
       * this by making sure that the host is in the domain.  We also require
       * that a domain have at least two periods to prevent domains of the form
       * ".com" and ".edu"
       *
       * Also make sure that there is more stuff after
       * the second dot to prevent ".com."
       */
      dot = PL_strchr(domain_from_header, '.');
      if(dot) {
        dot = PL_strchr(dot+1, '.');
      }
      if(!dot || *(dot+1) == '\0') {
        /* did not pass two dot test. FAIL */
        PR_FREEIF(path_from_header);
        PR_Free(domain_from_header);
        PR_Free(cur_path);
        PR_Free(cur_host);
        // TRACEMSG(("DOMAIN failed two dot test"));
        cookie_SetCookieStringInUse = PR_FALSE;
        cookie_Undefer();
        return;
      }

      /* strip port numbers from the current host for the domain test */
      colon = PL_strchr(cur_host, ':');
      if(colon) {
        *colon = '\0';
      }
      domain_length   = PL_strlen(domain_from_header);
      cur_host_length = PL_strlen(cur_host);

      /* check to see if the host is in the domain */
      if (!cookie_IsInDomain(domain_from_header, cur_host, cur_host_length)) {
          // TRACEMSG(("DOMAIN failed host within domain test."
//        " Domain: %s, Host: %s", domain_from_header, cur_host));
        PR_FREEIF(path_from_header);
        PR_Free(domain_from_header);
        PR_Free(cur_path);
        PR_Free(cur_host);
        cookie_SetCookieStringInUse = PR_FALSE;
        cookie_Undefer();
        return;
      }

      /*
       * check that portion of host not in domain does not contain a dot
       *    This satisfies the fourth requirement in section 4.3.2 of the cookie
       *    spec rfc 2109 (see www.cis.ohio-state.edu/htbin/rfc/rfc2109.html).
       *    It prevents host of the form x.y.co.nz from setting cookies in the
       *    entire .co.nz domain.  Note that this doesn't really solve the problem,
       *    it justs makes it more unlikely.  Sites such as y.co.nz can still set
       *    cookies for the entire .co.nz domain.
       */

      /*
       *  Although this is the right thing to do(tm), it breaks too many sites.  
       *  So only do it if the restrictCookieDomains pref is PR_TRUE.
       *
       */
      nsresult rv;
      NS_WITH_SERVICE(nsIPref, prefs, "component://netscape/preferences", &rv);
      if (NS_FAILED(prefs->GetBoolPref(cookie_strictDomainsPref, &pref_scd))) {
        pref_scd = PR_FALSE;
      }
      if ( pref_scd == PR_TRUE ) {
        cur_host[cur_host_length-domain_length] = '\0';
        dot = PL_strchr(cur_host, '.');
        cur_host[cur_host_length-domain_length] = '.';
        if (dot) {
        // TRACEMSG(("host minus domain failed no-dot test."
//          " Domain: %s, Host: %s", domain_from_header, cur_host));
          PR_FREEIF(path_from_header);
          PR_Free(domain_from_header);
          PR_Free(cur_path);
          PR_Free(cur_host);
          cookie_SetCookieStringInUse = PR_FALSE;
          cookie_Undefer();
          return;
        }
      }

      /* all tests passed, copy in domain to hostname field */
      StrAllocCopy(host_from_header, domain_from_header);
      isDomain = PR_TRUE;
      // TRACEMSG(("Accepted domain: %s", host_from_header));
      PR_Free(domain_from_header);
    }

    /* now search for the expires header 
     * NOTE: that this part of the parsing
     * destroys the original part of the string
     */
    ptr = PL_strcasestr(semi_colon, "expires=");
    if(ptr) {
      char *date =  ptr+8;
      /* terminate the string at the next semi-colon */
      for(ptr=date; *ptr != '\0'; ptr++) {
        if(*ptr == ';') {
          *ptr = '\0';
          break;
        }
      }
      if(timeToExpire == 0) {
        timeToExpire = cookie_ParseDate(date);
      }
      // TRACEMSG(("Have expires date: %ld", timeToExpire));
    }
  }
  if(!path_from_header) {
    /* strip down everything after the last slash to get the path. */
    char * slash = PL_strrchr(cur_path, '/');
    if(slash) {
      *slash = '\0';
    }
    path_from_header = cur_path;
  } else {
    PR_Free(cur_path);
  }
  if(!host_from_header) {
    host_from_header = cur_host;
  } else {
    PR_Free(cur_host);
  }

  /* keep cookies under the max bytes limit */
  if(PL_strlen(setCookieHeader) > MAX_BYTES_PER_COOKIE) {
    setCookieHeader[MAX_BYTES_PER_COOKIE-1] = '\0';
  }

  /* separate the name from the cookie */
  equal = PL_strchr(setCookieHeader, '=');
  if(equal) {
    *equal = '\0';
    StrAllocCopy(name_from_header, XP_StripLine(setCookieHeader));
    StrAllocCopy(cookie_from_header, XP_StripLine(equal+1));
  } else {
    // TRACEMSG(("Warning: no name found for cookie"));
    StrAllocCopy(cookie_from_header, XP_StripLine(setCookieHeader));
    StrAllocCopy(name_from_header, "");
  }

  /* generate the message for the nag box */
  PRUnichar * new_string=0;
  int count = cookie_Count(host_from_header);
  cookie_LockList();
  prev_cookie = cookie_CheckForPrevCookie
    (path_from_header, host_from_header, name_from_header);
  cookie_UnlockList();
  PRUnichar * message;
  if (prev_cookie) {
    message = cookie_Localize("PermissionToModifyCookie");
    new_string = nsTextFormatter::smprintf(message, host_from_header ? host_from_header : "");
  } else if (count>1) {
    message = cookie_Localize("PermissionToSetAnotherCookie");
    new_string = nsTextFormatter::smprintf(message, host_from_header ? host_from_header : "", count);
  } else if (count==1){
    message = cookie_Localize("PermissionToSetSecondCookie");
    new_string = nsTextFormatter::smprintf(message, host_from_header ? host_from_header : "");
  } else {
    message = cookie_Localize("PermissionToSetACookie");
    new_string = nsTextFormatter::smprintf(message, host_from_header ? host_from_header : "");
  }
  Recycle(message);

  //TRACEMSG(("mkaccess.c: Setting cookie: %s for host: %s for path: %s",
  //          cookie_from_header, host_from_header, path_from_header));

  /* use common code to determine if we can set the cookie */
  PRBool permission = permission_Check(aPrompter, host_from_header, COOKIEPERMISSION,
// I believe this is the right place to eventually add the logic to ask
// about cookies that have excessive lifetimes, but it shouldn't be done
// until generalized per-site preferences are available.
                                       //cookie_GetLifetimeAsk(timeToExpire) ||
                                       cookie_GetWarningPref(),
                                       new_string);
  PR_FREEIF(new_string);
  if (!permission) {
    PR_FREEIF(path_from_header);
    PR_FREEIF(host_from_header);
    PR_FREEIF(name_from_header);
    PR_FREEIF(cookie_from_header);
    cookie_SetCookieStringInUse = PR_FALSE;
    cookie_Undefer();
    return;
  }

  /* 
   * We have decided we are going to insert a cookie into the list
   *   Get the cookie lock so that we can munge on the list
   */
  cookie_LockList();

  /* limit the number of cookies from a specific host or domain */
  cookie_CheckForMaxCookiesFromHo(host_from_header);

  if (cookie_cookieList) {
    if(cookie_cookieList->Count() > MAX_NUMBER_OF_COOKIES-1) {
      cookie_RemoveOldestCookie();
    }
  }
  prev_cookie = cookie_CheckForPrevCookie (path_from_header, host_from_header, name_from_header);
  if(prev_cookie) {
    prev_cookie->expires = cookie_TrimLifetime(timeToExpire);
    PR_FREEIF(prev_cookie->cookie);
    PR_FREEIF(prev_cookie->path);
    PR_FREEIF(prev_cookie->host);
    PR_FREEIF(prev_cookie->name);
    prev_cookie->cookie = cookie_from_header;
    prev_cookie->path = path_from_header;
    prev_cookie->host = host_from_header;
    prev_cookie->name = name_from_header;
    prev_cookie->xxx = xxx;
    prev_cookie->isDomain = isDomain;
    prev_cookie->lastAccessed = get_current_time();
  } else {
    cookie_CookieStruct * tmp_cookie_ptr;
    size_t new_len;

    /* construct a new cookie_struct */
    prev_cookie = PR_NEW(cookie_CookieStruct);
    if(!prev_cookie) {
      PR_FREEIF(path_from_header);
      PR_FREEIF(host_from_header);
      PR_FREEIF(name_from_header);
      PR_FREEIF(cookie_from_header);
      cookie_UnlockList();
      cookie_SetCookieStringInUse = PR_FALSE;
      cookie_Undefer();
      return;
    }
    
    /* copy */
    prev_cookie->cookie = cookie_from_header;
    prev_cookie->name = name_from_header;
    prev_cookie->path = path_from_header;
    prev_cookie->host = host_from_header;
    prev_cookie->expires = cookie_TrimLifetime(timeToExpire);
    prev_cookie->xxx = xxx;
    prev_cookie->isDomain = isDomain;
    prev_cookie->lastAccessed = get_current_time();
    if(!cookie_cookieList) {
      cookie_cookieList = new nsVoidArray();
      if(!cookie_cookieList) {
        PR_FREEIF(path_from_header);
        PR_FREEIF(name_from_header);
        PR_FREEIF(host_from_header);
        PR_FREEIF(cookie_from_header);
        PR_Free(prev_cookie);
        cookie_UnlockList();
        cookie_SetCookieStringInUse = PR_FALSE;
        cookie_Undefer();
        return;
      }
    }

    /* add it to the list so that it is before any strings of smaller length */
    bCookieAdded = PR_FALSE;
    new_len = PL_strlen(prev_cookie->path);
    PRInt32 count = cookie_cookieList->Count();
    for (PRInt32 i = 0; i < count; ++i) {
      tmp_cookie_ptr = NS_STATIC_CAST(cookie_CookieStruct*, cookie_cookieList->ElementAt(i));
      if (tmp_cookie_ptr) {
        if(new_len > PL_strlen(tmp_cookie_ptr->path)) {
          cookie_cookieList->InsertElementAt(prev_cookie, i);
          bCookieAdded = PR_TRUE;
          break;
        }
      }
    }
    if ( !bCookieAdded ) {
      /* no shorter strings found in list */
      cookie_cookieList->AppendElement(prev_cookie);
    }
  }

  /* At this point we know a cookie has changed. Write the cookies to file. */
  cookie_cookiesChanged = PR_TRUE;
  cookie_Save();
  cookie_UnlockList();
  cookie_SetCookieStringInUse = PR_FALSE;
  cookie_Undefer();
  return;
}

PUBLIC void
COOKIE_SetCookieString(char * curURL, char * setCookieHeader) {
  cookie_SetCookieString(curURL, 0, setCookieHeader, 0);
}

/* This function wrapper wraps COOKIE_SetCookieString for the purposes of 
 * determining whether or not a cookie is inline (we need the URL struct, 
 * and outputFormat to do so).  this is called from NET_ParseMimeHeaders 
 * in mkhttp.c
 * This routine does not need to worry about the cookie lock since all of
 *   the work is handled by sub-routines
*/

PUBLIC void
COOKIE_SetCookieStringFromHttp(char * curURL, char * firstURL, nsIPrompt *aPrompter, char * setCookieHeader, char * server_date) {

  /* allow for multiple cookies separated by newlines */
   char *newline = PL_strchr(setCookieHeader, '\n');
   if(newline) {
     *newline = '\0';
     COOKIE_SetCookieStringFromHttp(curURL, firstURL, aPrompter, setCookieHeader, server_date);
     *newline = '\n';
     COOKIE_SetCookieStringFromHttp(curURL, firstURL, aPrompter, newline+1, server_date);
     return;
   }

  /* If the outputFormat is not PRESENT (the url is not going to the screen), and not
   *  SAVE AS (shift-click) then 
   *  the cookie being set is defined as inline so we need to do what the user wants us
   *  to based on his preference to deal with foreign cookies. If it's not inline, just set
   *  the cookie.
   */
  char *ptr=NULL;
  time_t gmtCookieExpires=0, expires=0, sDate;

  /* check for foreign cookie if pref says to reject such */
  if ((COOKIE_GetBehaviorPref() == COOKIE_DontAcceptForeign) &&
      cookie_isForeign(curURL, firstURL)) {
    /* it's a foreign cookie so don't set the cookie */
    return;
  }

  /* Determine when the cookie should expire. This is done by taking the difference between 
   * the server time and the time the server wants the cookie to expire, and adding that 
   * difference to the client time. This localizes the client time regardless of whether or
   * not the TZ environment variable was set on the client.
   */

  /* Get the time the cookie is supposed to expire according to the attribute*/
  ptr = PL_strcasestr(setCookieHeader, "expires=");
  if(ptr) {
    char *date =  ptr+8;
    char origLast = '\0';
    for(ptr=date; *ptr != '\0'; ptr++) {
      if(*ptr == ';') {
        origLast = ';';
        *ptr = '\0';
        break;
      }
    }
    expires = cookie_ParseDate(date);
    *ptr=origLast;
  }
  if (server_date) {
    sDate = cookie_ParseDate(server_date);
  } else {
    sDate = get_current_time();
  }
  if( sDate && expires ) {
    if( expires < sDate ) {
      gmtCookieExpires=1;
    } else {
      gmtCookieExpires = expires - sDate + get_current_time();
      // if overflow 
      if( gmtCookieExpires < get_current_time()) {
        gmtCookieExpires = (((unsigned) (~0) << 1) >> 1); // max int 
      }
    }
  }
  cookie_SetCookieString(curURL, aPrompter, setCookieHeader, gmtCookieExpires);
}

/* saves the HTTP cookies permissions to disk */
PRIVATE void
permission_Save() {
  permission_HostStruct * hostStruct;
  permission_TypeStruct * typeStruct;

  if (COOKIE_GetBehaviorPref() == COOKIE_DontUse) {
    return;
  }
  if (!cookie_permissionsChanged) {
    return;
  }
  permission_LockList();
  if (cookie_permissionList == nsnull) {
    permission_UnlockList();
    return;
  }
  nsFileSpec dirSpec;
  nsresult rval = cookie_ProfileDirectory(dirSpec);
  if (NS_FAILED(rval)) {
    permission_UnlockList();
    return;
  }
  nsOutputFileStream strm(dirSpec + "cookperm.txt");
  if (!strm.is_open()) {
    permission_UnlockList();
    return;
  }

  {
    nsAutoString temp1; temp1.AssignWithConversion("# HTTP Cookie Permission File\n");
    cookie_Put(strm, temp1);
  }

  {
    nsAutoString temp2; temp2.AssignWithConversion("# http://www.netscape.com/newsref/std/cookie_spec.html\n");
    cookie_Put(strm, temp2);
  }

  {
    nsAutoString temp3; temp3.AssignWithConversion("# This is a generated file!  Do not edit.\n\n");
    cookie_Put(strm, temp3);
  }

  /* format shall be:
   * host \t permission \t permission ... \n
   */

  
  PRInt32 count = cookie_permissionList->Count();
  for (PRInt32 i = 0; i < count; ++i) {
    hostStruct = NS_STATIC_CAST(permission_HostStruct*, cookie_permissionList->ElementAt(i));
    if (hostStruct) {
      {
        nsAutoString temp4; temp4.AssignWithConversion(hostStruct->host);
        cookie_Put(strm, temp4);
      }

      PRInt32 count2 = hostStruct->permissionList->Count();
      for (PRInt32 typeIndex=0; typeIndex<count2; typeIndex++) {
        typeStruct = NS_STATIC_CAST
          (permission_TypeStruct*, hostStruct->permissionList->ElementAt(typeIndex));
        {
          nsAutoString temp5; temp5.AssignWithConversion("\t");
          cookie_Put(strm, temp5);
        }
        cookie_PutInt(strm, typeStruct->type);
        if (typeStruct->permission) {
          nsAutoString temp6; temp6.AssignWithConversion("T");
          cookie_Put(strm, temp6);
        } else {
          nsAutoString temp7; temp7.AssignWithConversion("F");
          cookie_Put(strm, temp7);
        }
      }

      nsAutoString temp8; temp8.AssignWithConversion("\n");
      cookie_Put(strm, temp8);
    }
  }

  /* save current state of nag boxs' checkmarks */
  {
    nsAutoString temp9; temp9.AssignWithConversion("@@@@");
    cookie_Put(strm, temp9);
  }
  for (PRInt32 type = 0; type < NUMBER_OF_PERMISSIONS; type++) {
    {
      nsAutoString temp10; temp10.AssignWithConversion("\t");
      cookie_Put(strm, temp10);
    }
    cookie_PutInt(strm, type);
    if (permission_GetRememberChecked(type)) {
      nsAutoString temp11; temp11.AssignWithConversion("T");
      cookie_Put(strm, temp11);
    } else {
      nsAutoString temp12; temp12.AssignWithConversion("F");
      cookie_Put(strm, temp12);
    }
  }

  {
    nsAutoString temp13; temp13.AssignWithConversion("\n");
    cookie_Put(strm, temp13);
  }

  cookie_permissionsChanged = PR_FALSE;
  strm.flush();
  strm.close();
  permission_UnlockList();
}

/* reads the HTTP cookies permission from disk */
PRIVATE void
permission_Load() {
  nsAutoString buffer;
  nsFileSpec dirSpec;
  nsresult rv = cookie_ProfileDirectory(dirSpec);
  if (NS_FAILED(rv)) {
    return;
  }
  nsInputFileStream strm(dirSpec + "cookperm.txt");
  if (!strm.is_open()) {
    /* file doesn't exist -- that's not an error */
    for (PRInt32 type=0; type<NUMBER_OF_PERMISSIONS; type++) {
      permission_SetRememberChecked(type, PR_FALSE);
    }
    return;
  }

  /* format is:
   * host \t number permission \t number permission ... \n
   * if this format isn't respected we move onto the next line in the file.
   */
  permission_LockList();
  while(cookie_GetLine(strm,buffer) != -1) {
    if ( !buffer.IsEmpty() ) {
      PRUnichar firstChar = buffer.CharAt(0);
      if (firstChar == '#' || firstChar == CR ||
          firstChar == LF || firstChar == 0) {
        continue;
      }
    }

    int hostIndex, permissionIndex, nextPermissionIndex;
    hostIndex = 0;

    if ((permissionIndex=buffer.FindChar('\t', PR_FALSE, hostIndex)+1) == 0) {
      continue;      
    }

    /* ignore leading periods in host name */
    while (hostIndex < permissionIndex && (buffer.CharAt(hostIndex) == '.')) {
      hostIndex++;
    }

    nsString host;
    buffer.Mid(host, hostIndex, permissionIndex-hostIndex-1);

    nsString permissionString;
    nextPermissionIndex = 0;
    for (;;) {
      if (nextPermissionIndex == buffer.Length()+1) {
        break;
      }
      if ((nextPermissionIndex=buffer.FindChar('\t', PR_FALSE, permissionIndex)+1) == 0) {
        nextPermissionIndex = buffer.Length()+1;
      }
      buffer.Mid(permissionString, permissionIndex, nextPermissionIndex-permissionIndex-1);
      permissionIndex = nextPermissionIndex;

      PRInt32 type = 0;
      PRUint32 index = 0;

      if (permissionString.IsEmpty()) {
        continue; /* empty permission entry -- should never happen */
      }
      char c = (char)permissionString.CharAt(index);
      while (index < permissionString.Length() && c >= '0' && c <= '9') {
        type = 10*type + (c-'0');
        c = (char)permissionString.CharAt(++index);
      }
      if (index >= permissionString.Length()) {
        continue; /* bad format for this permission entry */
      }
      PRBool permission = (permissionString.CharAt(index) == 'T');

      /*
       * a host value of "@@@@" is a special code designating the
       * state of the cookie nag-box's checkmark
       */
      if (host.EqualsWithConversion("@@@@")) {
        if (!permissionString.IsEmpty()) {
          permission_SetRememberChecked(type, permission);
        }
      } else {
        if (!permissionString.IsEmpty()) {
          rv = permission_Add(host.ToNewCString(), permission, type, PR_FALSE);
          if (NS_FAILED(rv)) {
            permission_UnlockList();
            strm.close();
            return;
          }
        }
      }
    }
  }

  strm.close();
  permission_UnlockList();
  cookie_permissionsChanged = PR_FALSE;
  return;
}

/* saves out the HTTP cookies to disk */
PRIVATE void
cookie_Save() {
  cookie_CookieStruct * cookie_s;
  time_t cur_date = get_current_time();
  char date_string[36];
  if (COOKIE_GetBehaviorPref() == COOKIE_DontUse) {
    return;
  }
  if (!cookie_cookiesChanged) {
    return;
  }
  cookie_LockList();
  if (cookie_cookieList == nsnull) {
    cookie_UnlockList();
    return;
  }
  nsFileSpec dirSpec;
  nsresult rv = cookie_ProfileDirectory(dirSpec);
  if (NS_FAILED(rv)) {
    cookie_UnlockList();
    return;
  }
  nsOutputFileStream strm(dirSpec + "cookies.txt");
  if (!strm.is_open()) {
    /* file doesn't exist -- that's not an error */
    cookie_UnlockList();
    return;
  }

  {
    nsAutoString temp1; temp1.AssignWithConversion("# HTTP Cookie File\n");
    cookie_Put(strm, temp1);
  }

  {
    nsAutoString temp2; temp2.AssignWithConversion("# http://www.netscape.com/newsref/std/cookie_spec.html\n");
    cookie_Put(strm, temp2);
  }

  {
    nsAutoString temp3; temp3.AssignWithConversion("# This is a generated file!  Do not edit.\n\n");
    cookie_Put(strm, temp3);
  }

  /* format shall be:
   *
   * host \t isDomain \t path \t xxx \t expires \t name \t cookie
   *
   * isDomain is PR_TRUE or PR_FALSE
   * xxx is PR_TRUE or PR_FALSE
   * expires is a time_t integer
   * cookie can have tabs
   */
  PRInt32 count = cookie_cookieList->Count();
  for (PRInt32 i = 0; i < count; ++i) {
    cookie_s = NS_STATIC_CAST(cookie_CookieStruct*, cookie_cookieList->ElementAt(i));
    if (cookie_s) {
      if (cookie_s->expires < cur_date) {
        /* don't write entry if cookie has expired or has no expiration date */
        continue;
      }

      {
        nsAutoString temp4; temp4.AssignWithConversion(cookie_s->host);
        cookie_Put(strm, temp4);
      }
      if (cookie_s->isDomain) {
        nsAutoString temp5; temp5.AssignWithConversion("\tTRUE\t");
        cookie_Put(strm, temp5);
      } else {
        nsAutoString temp6; temp6.AssignWithConversion("\tFALSE\t");
        cookie_Put(strm, temp6);
      }

      {
        nsAutoString temp7; temp7.AssignWithConversion(cookie_s->path);
        cookie_Put(strm, temp7);
      }

      if (cookie_s->xxx) {
        nsAutoString temp8; temp8.AssignWithConversion("\tTRUE\t");
        cookie_Put(strm, temp8);
      } else {
        nsAutoString temp9; temp9.AssignWithConversion("\tFALSE\t");
        cookie_Put(strm, temp9);
      }

      PR_snprintf(date_string, sizeof(date_string), "%lu", cookie_s->expires);

        // STRING USE WARNING: might want to hoist TRUE and FALSE strings et al, above and elsewhere in this file,
        //  as with tab here or better yet, just use |nsLiteralString| directly when that becomes possible
        //  |cookie_Put| would have to be fixed to take a |nsAReadableString|, but the end result would be a win.
      nsAutoString tempTabString; tempTabString.AssignWithConversion("\t");

      {
        nsAutoString temp10; temp10.AssignWithConversion(date_string);
        cookie_Put(strm, temp10);
      }

      cookie_Put(strm, tempTabString);

      {
        nsAutoString temp10; temp10.AssignWithConversion(cookie_s->name);
        cookie_Put(strm, temp10);
      }

      cookie_Put(strm, tempTabString);

      {
        nsAutoString temp11; temp11.AssignWithConversion(cookie_s->cookie);
        cookie_Put(strm, temp11);
      }

      nsAutoString temp12; temp12.AssignWithConversion("\n");
      cookie_Put(strm, temp12);
    }
  }

  cookie_cookiesChanged = PR_FALSE;
  strm.flush();
  strm.close();
  cookie_UnlockList();
}

/* reads HTTP cookies from disk */
PRIVATE void
cookie_Load() {
  cookie_CookieStruct *new_cookie, *tmp_cookie_ptr;
  size_t new_len;
  nsAutoString buffer;
  PRBool added_to_list;
  nsFileSpec dirSpec;
  nsresult rv = cookie_ProfileDirectory(dirSpec);
  if (NS_FAILED(rv)) {
    return;
  }
  nsInputFileStream strm(dirSpec + "cookies.txt");
  if (!strm.is_open()) {
    /* file doesn't exist -- that's not an error */
    return;
  }
  cookie_LockList();

  /* format is:
   *
   * host \t isDomain \t path \t xxx \t expires \t name \t cookie
   *
   * if this format isn't respected we move onto the next line in the file.
   * isDomain is PR_TRUE or PR_FALSE -- defaulting to PR_FALSE
   * xxx is PR_TRUE or PR_FALSE   -- should default to PR_TRUE
   * expires is a time_t integer
   * cookie can have tabs
   */
  while (cookie_GetLine(strm,buffer) != -1){
    added_to_list = PR_FALSE;

    if ( !buffer.IsEmpty() ) {
      PRUnichar firstChar = buffer.CharAt(0);
      if (firstChar == '#' || firstChar == CR ||
          firstChar == LF || firstChar == 0) {
        continue;
      }
    }
    int hostIndex, isDomainIndex, pathIndex, xxxIndex, expiresIndex, nameIndex, cookieIndex;
    hostIndex = 0;
    if ((isDomainIndex=buffer.FindChar('\t', PR_FALSE,hostIndex)+1) == 0 ||
        (pathIndex=buffer.FindChar('\t', PR_FALSE,isDomainIndex)+1) == 0 ||
        (xxxIndex=buffer.FindChar('\t', PR_FALSE,pathIndex)+1) == 0 ||
        (expiresIndex=buffer.FindChar('\t', PR_FALSE,xxxIndex)+1) == 0 ||
        (nameIndex=buffer.FindChar('\t', PR_FALSE,expiresIndex)+1) == 0 ||
        (cookieIndex=buffer.FindChar('\t', PR_FALSE,nameIndex)+1) == 0 ) {
      continue;
    }
    nsAutoString host, isDomain, path, xxx, expires, name, cookie;
    buffer.Mid(host, hostIndex, isDomainIndex-hostIndex-1);
    buffer.Mid(isDomain, isDomainIndex, pathIndex-isDomainIndex-1);
    buffer.Mid(path, pathIndex, xxxIndex-pathIndex-1);
    buffer.Mid(xxx, xxxIndex, expiresIndex-xxxIndex-1);
    buffer.Mid(expires, expiresIndex, nameIndex-expiresIndex-1);
    buffer.Mid(name, nameIndex, cookieIndex-nameIndex-1);
    buffer.Mid(cookie, cookieIndex, buffer.Length()-cookieIndex);

    /* create a new cookie_struct and fill it in */
    new_cookie = PR_NEW(cookie_CookieStruct);
    if (!new_cookie) {
      cookie_UnlockList();
      strm.close();
      return;
    }
    memset(new_cookie, 0, sizeof(cookie_CookieStruct));
    new_cookie->name = name.ToNewCString();
    new_cookie->cookie = cookie.ToNewCString();
    new_cookie->host = host.ToNewCString();
    new_cookie->path = path.ToNewCString();
    if (isDomain.EqualsWithConversion("TRUE")) {
      new_cookie->isDomain = PR_TRUE;
    } else {
      new_cookie->isDomain = PR_FALSE;
    }
    if (xxx.EqualsWithConversion("TRUE")) {
      new_cookie->xxx = PR_TRUE;
    } else {
      new_cookie->xxx = PR_FALSE;
    }
    char * expiresCString = expires.ToNewCString();
    new_cookie->expires = strtoul(expiresCString, nsnull, 10);
    nsCRT::free(expiresCString);

    /* start new cookie list if one does not already exist */
    if (!cookie_cookieList) {
      cookie_cookieList = new nsVoidArray();
      if (!cookie_cookieList) {
        cookie_UnlockList();
        strm.close();
        return;
      }
    }

    /* add new cookie to the list so that it is before any strings of smaller length */
    new_len = PL_strlen(new_cookie->path);
    PRInt32 count = cookie_cookieList->Count();
    for (PRInt32 i = 0; i < count; ++i) {
      tmp_cookie_ptr = NS_STATIC_CAST(cookie_CookieStruct*, cookie_cookieList->ElementAt(i));
      if (tmp_cookie_ptr) {
        if (new_len > PL_strlen(tmp_cookie_ptr->path)) {
          cookie_cookieList->InsertElementAt(new_cookie, i);
          added_to_list = PR_TRUE;
          break;
        }
      }
    }

    /* no shorter strings found in list so add new cookie at end */
    if (!added_to_list) {
      cookie_cookieList->AppendElement(new_cookie);
    }
  }

  strm.close();
  cookie_UnlockList();
  cookie_cookiesChanged = PR_FALSE;
  return;
}


PUBLIC int
COOKIE_ReadCookies()
{
  static PRBool sReadCookies = PR_FALSE;
  
  if (sReadCookies)
    NS_WARNING("We are reading the cookies more than once. Probably bad");
    
  cookie_Load();
  permission_Load();
  sReadCookies = PR_TRUE;
  return 0;
}

PRIVATE PRBool
CookieCompare (cookie_CookieStruct * cookie1, cookie_CookieStruct * cookie2) {
  char * host1 = cookie1->host;
  char * host2 = cookie2->host;

  /* get rid of leading period on host name, if any */
  if (*host1 == '.') {
    host1++;
  }
  if (*host2 == '.') {
    host2++;
  }

  /* make decision based on host name if they are unequal */
  if (PL_strcmp (host1, host2) < 0) {
    return -1;
  }
  if (PL_strcmp (host1, host2) > 0) {
    return 1;
  }

  /* if host names are equal, make decision based on cookie name if they are unequal */
  if (PL_strcmp (cookie1->name, cookie2->name) < 0) {
    return -1;
  }
  if (PL_strcmp (cookie1->name, cookie2->name) > 0) {
    return 1;
  }

  
  /* if host and cookie names are equal, make decision based on path */
  /*    It may seem like this should never happen but it does.
   *    Go to groups.aol.com and you will get two cookies with
   *    identical host and cookie names but different paths
   */
  return (PL_strcmp (cookie1->path, cookie2->path));
}

/*
 * find the next cookie that is alphabetically after the specified cookie
 *    ordering is based on hostname and the cookie name
 *    if specified cookie is NULL, find the first cookie alphabetically
 */
PRIVATE cookie_CookieStruct *
NextCookieAfter(cookie_CookieStruct * cookie, int * cookieNum) {
  cookie_CookieStruct *cookie_ptr;
  cookie_CookieStruct *lowestCookie = NULL;
  int localCookieNum = 0;

  if (cookie_cookieList == nsnull) 
    return NULL;
  PRInt32 count = cookie_cookieList->Count();
  for (PRInt32 i = 0; i < count; ++i) {
    cookie_ptr = NS_STATIC_CAST(cookie_CookieStruct*, cookie_cookieList->ElementAt(i));
    if (cookie_ptr) {
      if (!cookie || (CookieCompare(cookie_ptr, cookie) > 0)) {
        if (!lowestCookie || (CookieCompare(cookie_ptr, lowestCookie) < 0)) {
          lowestCookie = cookie_ptr;
          *cookieNum = localCookieNum;
        }
      }
      localCookieNum++;
    }
  }

  return lowestCookie;
}

/*
 * return a string that has each " of the argument sting
 * replaced with \" so it can be used inside a quoted string
 */
PRIVATE char*
cookie_FixQuoted(char* s) {
  char * result;
  int count = PL_strlen(s);
  char *quote = s;
  unsigned int i, j;
  while ((quote = PL_strchr(quote, '"'))) {
    count++;
    quote++;
  }
  result = (char*)PR_Malloc(count + 1);
  for (i=0, j=0; i<PL_strlen(s); i++) {
    if (s[i] == '"') {
      result[i+(j++)] = '\\';
    }
    result[i+j] = s[i];
  }
  result[i+j] = '\0';
  return result;
}

/* return PR_TRUE if "number" is in sequence of comma-separated numbers */
PRIVATE
PRBool cookie_InSequence(char* sequence, int number) {
  char* ptr;
  char* endptr;
  char* undo = NULL;
  PRBool retval = PR_FALSE;
  int i;

  /* not necessary -- routine will work even with null sequence */
  if (!*sequence) {
    return PR_FALSE;
  }

  for (ptr = sequence ; ptr ; ptr = endptr) {

    /* get to next comma */
    endptr = PL_strchr(ptr, ',');

    /* if comma found, process it */
    if (endptr) {

      /* restore last comma-to-null back to comma */
      if (undo) {
        *undo = ',';
      }

      /* set the comma to a null */
      undo = endptr;
      *endptr++ = '\0';

      /* compare the number before the comma with "number" */
      if (*ptr) {
        i = atoi(ptr);
        if (i == number) {

          /* "number" was in the sequence so return PR_TRUE */
          retval = PR_TRUE;
          break;
        }
      }
    }
  }

  /* restore last comma-to-null back to comma */
  if (undo) {
      *undo = ',';
  }
  return retval;
}

PRIVATE char*
cookie_FindValueInArgs(nsAutoString results, char* name) {
  /* note: name must start and end with a vertical bar */
  nsAutoString value;
  PRInt32 start, length;
  start = results.Find(name);
  NS_ASSERTION(start >= 0, "bad data");
  if (start < 0) {
    return nsCAutoString().ToNewCString();
  }
  start += PL_strlen(name); /* get passed the |name| part */
  length = results.FindChar('|', PR_FALSE,start) - start;
  results.Mid(value, start, length);
  return value.ToNewCString();
}

PUBLIC void
COOKIE_CookieViewerReturn(nsAutoString results) {
  cookie_CookieStruct * cookie;
  PRInt32 count = 0;

  /* step through all cookies and delete those that are in the sequence */
  char * gone = cookie_FindValueInArgs(results, "|goneC|");
  char * block = cookie_FindValueInArgs(results, "|block|");
  cookie_LockList();
  if (cookie_cookieList) {
    count = cookie_cookieList->Count();
    while (count>0) {
      count--;
      cookie = NS_STATIC_CAST(cookie_CookieStruct*, cookie_cookieList->ElementAt(count));
      if (cookie && cookie_InSequence(gone, count)) {
        if (PL_strlen(block) && block[0]=='t') {
          char * hostname = nsnull;
          StrAllocCopy(hostname, cookie->host);
          permission_Add(hostname, PR_FALSE, COOKIEPERMISSION, PR_TRUE);
        }
        cookie_FreeCookie(cookie);
        cookie_cookiesChanged = PR_TRUE;
      }
    }
  }
  cookie_Save();
  cookie_UnlockList();
  nsCRT::free(gone);
  nsCRT::free(block);

  /* step through all permissions and delete those that are in the sequence */
  for (PRInt32 type = 0; type < NUMBER_OF_PERMISSIONS; type++) {
    switch (type) {
      case COOKIEPERMISSION:
        gone = cookie_FindValueInArgs(results, "|goneP|");
        break;
      case IMAGEPERMISSION:
        gone = cookie_FindValueInArgs(results, "|goneI|");
        break;
    }
    permission_LockList();
    if (cookie_permissionList) {
      count = cookie_permissionList->Count();
      while (count>0) {
        count--;
        if (cookie_InSequence(gone, count)) {
          permission_Free(count, type, PR_FALSE);
          cookie_permissionsChanged = PR_TRUE;
        }
      }
    }
    permission_Save();
    permission_UnlockList();
    nsCRT::free(gone);
  }
}

#define BUFLEN2 5000
#define BREAK '\001'

PUBLIC void
COOKIE_GetCookieListForViewer(nsString& aCookieList) {
  PRUnichar *buffer = (PRUnichar*)PR_Malloc(2*BUFLEN2);
  int g, cookieNum;
  cookie_CookieStruct * cookie;

  cookie_LockList();

  /* Get rid of any expired cookies now so user doesn't
   * think/see that we're keeping cookies in memory.
   */
  cookie_RemoveExpiredCookies();

  /* generate the list of cookies in alphabetical order */
  cookie = NULL;
  while ((cookie = NextCookieAfter(cookie, &cookieNum))) {
    char *fixed_name = cookie_FixQuoted(cookie->name);
    char *fixed_value = cookie_FixQuoted(cookie->cookie);
    char *fixed_domain_or_host = cookie_FixQuoted(cookie->host);
    char *fixed_path = cookie_FixQuoted(cookie->path);
    PRUnichar * Domain = cookie_Localize("Domain");
    PRUnichar * Host = cookie_Localize("Host");
    PRUnichar * Yes = cookie_Localize("Yes");
    PRUnichar * No = cookie_Localize("No");
    PRUnichar * AtEnd = cookie_Localize("AtEndOfSession");
    buffer[0] = '\0';
    g = 0;

    nsString temp1; temp1.AssignWithConversion("%c%d%c%s%c%s%c%S%c%s%c%s%c%S%c%S");
    nsString temp2; temp2.AssignWithConversion(ctime(&(cookie->expires)));
    g += nsTextFormatter::snprintf(buffer+g, BUFLEN2-g,
      temp1.GetUnicode(),
      BREAK, cookieNum,
      BREAK, fixed_name,
      BREAK, fixed_value,
      BREAK, cookie->isDomain ? Domain : Host,
      BREAK, fixed_domain_or_host,
      BREAK, fixed_path,
      BREAK, cookie->xxx ? Yes : No,
      BREAK, cookie->expires ? (temp2.GetUnicode()) : AtEnd
    );
    PR_FREEIF(fixed_name);
    PR_FREEIF(fixed_value);
    PR_FREEIF(fixed_domain_or_host);
    PR_FREEIF(fixed_path);
    Recycle(Domain);
    Recycle(Host);
    Recycle(Yes);
    Recycle(No);
    Recycle(AtEnd);
    aCookieList += buffer;
  }
  PR_FREEIF(buffer);
  cookie_UnlockList();
}

PUBLIC void
COOKIE_GetPermissionListForViewer(nsString& aPermissionList, PRInt32 type) {
  char *buffer = (char*)PR_Malloc(BUFLEN2);
  int g = 0, permissionNum;
  permission_HostStruct * hostStruct;
  permission_TypeStruct * typeStruct;

  permission_LockList();
  buffer[0] = '\0';
  permissionNum = 0;

  if (cookie_permissionList == nsnull) {
    permission_UnlockList();
    return;
  }

  PRInt32 count = cookie_permissionList->Count();
  for (PRInt32 i = 0; i < count; ++i) {
    hostStruct = NS_STATIC_CAST(permission_HostStruct*, cookie_permissionList->ElementAt(i));
    if (hostStruct) {
      PRInt32 count2 = hostStruct->permissionList->Count();
      for (PRInt32 typeIndex=0; typeIndex<count2; typeIndex++) {
        typeStruct = NS_STATIC_CAST
          (permission_TypeStruct*, hostStruct->permissionList->ElementAt(typeIndex));
        if (typeStruct->type == type) {
          g += PR_snprintf(buffer+g, BUFLEN2-g,
            "%c%d%c%c%s",
            BREAK,
            permissionNum,
            BREAK,
            (typeStruct->permission) ? '+' : '-',
            hostStruct->host
          );
          permissionNum++;
        }
      }
    }
  }
  aPermissionList.AssignWithConversion(buffer);
  PR_FREEIF(buffer);
  permission_UnlockList();
}

PUBLIC void
Image_Block(nsString imageURL) {
  if (imageURL.Length() == 0) {
    return;
  }
  char * imageURLCString = imageURL.ToNewCString();
  char *host = cookie_ParseURL(imageURLCString, GET_HOST_PART);
  Recycle(imageURLCString);
  if (PL_strlen(host) != 0) {
    permission_Add(host, PR_FALSE, IMAGEPERMISSION, PR_TRUE);
  }
}

PUBLIC void
Permission_Add(nsString imageURL, PRBool permission, PRInt32 type) {
  if (imageURL.Length() == 0) {
    return;
  }
  char * imageURLCString = imageURL.ToNewCString();
  char *host = cookie_ParseURL(imageURLCString, GET_HOST_PART);
  Recycle(imageURLCString);
  if (PL_strlen(host) != 0) {
    permission_Add(host, permission, type, PR_TRUE);
  }
}

/* Hack - Neeti remove this */
/* remove front and back white space
 * modifies the original string
 */
PUBLIC char *
XP_StripLine (char *string) {
  char * ptr;

  /* remove leading blanks */
  while(*string=='\t' || *string==' ' || *string=='\r' || *string=='\n') {
    string++;
  }

  for(ptr=string; *ptr; ptr++) {
    ;   /* NULL BODY; Find end of string */
  }

  /* remove trailing blanks */
  for(ptr--; ptr >= string; ptr--) {
    if(*ptr=='\t' || *ptr==' ' || *ptr=='\r' || *ptr=='\n') { 
      *ptr = '\0'; 
    } else { 
      break;
    }
  }
  return string;
}

/* Very similar to strdup except it free's too
 */
PUBLIC char * 
NET_SACopy (char **destination, const char *source) {
  if(*destination) {
    PR_Free(*destination);
    *destination = 0;
  }
  if (! source) {
    *destination = NULL;
  } else {
    *destination = (char *) PR_Malloc (PL_strlen(source) + 1);
    if (*destination == NULL) {
      return(NULL);
    }
    PL_strcpy (*destination, source);
  }
  return *destination;
}

/*  Again like strdup but it concatinates and free's and uses Realloc
*/
PUBLIC char *
NET_SACat (char **destination, const char *source) {
  if (source && *source) {
    if (*destination) {
      int length = PL_strlen (*destination);
      *destination = (char *) PR_Realloc (*destination, length + PL_strlen(source) + 1);
      if (*destination == NULL) {
        return(NULL);
      }

      PL_strcpy (*destination + length, source);
    } else {
      *destination = (char *) PR_Malloc (PL_strlen(source) + 1);
      if (*destination == NULL) {
        return(NULL);
      }
      PL_strcpy (*destination, source);
    }
  }
  return *destination;
}

MODULE_PRIVATE time_t 
cookie_ParseDate(char *date_string) {

#ifndef USE_OLD_TIME_FUNC

  PRTime prdate;
  time_t date = 0;
  // TRACEMSG(("Parsing date string: %s\n",date_string));

  /* try using PR_ParseTimeString instead */
  if(PR_ParseTimeString(date_string, PR_TRUE, &prdate) == PR_SUCCESS) {
    PRInt64 r, u;
    LL_I2L(u, PR_USEC_PER_SEC);
    LL_DIV(r, prdate, u);
    LL_L2I(date, r);
    if (date < 0) {
      date = 0;
    }
    // TRACEMSG(("Parsed date as GMT: %s\n", asctime(gmtime(&date))));
    // TRACEMSG(("Parsed date as local: %s\n", ctime(&date)));
  } else {
    // TRACEMSG(("Could not parse date"));
  }
  return (date);

#else

  struct tm time_info; /* Points to static tm structure */
  char  *ip;
  char   mname[256];
  time_t rv;
  // TRACEMSG(("Parsing date string: %s\n",date_string));
  memset(&time_info, 0, sizeof(struct tm));

  /* Whatever format we're looking at, it will start with weekday. */
  /* Skip to first space. */
  if(!(ip = PL_strchr(date_string,' '))) {
    return 0;
  } else {
    while(COOKIE_IS_SPACE(*ip)) {
      ++ip;
    }
  }
  /* make sure that the date is less than 256.  That will keep name from ever overflowing */
  if(255 < PL_strlen(ip)) {
    return(0);
  }
  if(isalpha(*ip)) {
    /* ctime */
    sscanf(ip, (strstr(ip, "DST") ? "%s %d %d:%d:%d %*s %d" : "%s %d %d:%d:%d %d"),
           mname, &time_info.tm_mday, &time_info.tm_hour, &time_info.tm_min,
           &time_info.tm_sec, &time_info.tm_year);
    time_info.tm_year -= 1900;
  } else if(ip[2] == '-') {
    /* RFC 850 (normal HTTP) */
    char t[256];
    sscanf(ip,"%s %d:%d:%d", t, &time_info.tm_hour, &time_info.tm_min, &time_info.tm_sec);
    t[2] = '\0';
    time_info.tm_mday = atoi(t);
    t[6] = '\0';
    PL_strcpy(mname,&t[3]);
    time_info.tm_year = atoi(&t[7]);
    /* Prevent wraparound from ambiguity */
    if(time_info.tm_year < 70) {
      time_info.tm_year += 100;
    } else if(time_info.tm_year > 1900) {
      time_info.tm_year -= 1900;
    }
  } else {
    /* RFC 822 */
    sscanf(ip,"%d %s %d %d:%d:%d",&time_info.tm_mday, mname, &time_info.tm_year,
           &time_info.tm_hour, &time_info.tm_min, &time_info.tm_sec);
    /* since tm_year is years since 1900 and the year we parsed
     * is absolute, we need to subtract 1900 years from it
     */
    time_info.tm_year -= 1900;
  }
  time_info.tm_mon = NET_MonthNo(mname);
  if(time_info.tm_mon == -1) { /* check for error */
    return(0);
  }
  // TRACEMSG(("Parsed date as: %s\n", asctime(&time_info)));
  rv = mktime(&time_info);
  if(time_info.tm_isdst) {
    rv -= 3600;
  }
  if(rv == -1) {
    // TRACEMSG(("mktime was unable to resolve date/time: %s\n", asctime(&time_info)));
    return(0);
  } else {
    // TRACEMSG(("Parsed date %s\n", asctime(&time_info)));
    // TRACEMSG(("\tas %s\n", ctime(&rv)));
    return(rv);
  }

#endif
}

/* Also skip '>' as part of host name */
MODULE_PRIVATE char * 
cookie_ParseURL (const char *url, int parts_requested) {
  char *rv=0,*colon, *slash, *ques_mark, *hash_mark;
  char *atSign, *host, *passwordColon, *gtThan;
  assert(url);
  if(!url) {
    return(StrAllocCat(rv, ""));
  }
  colon = PL_strchr(url, ':'); /* returns a const char */
  /* Get the protocol part, not including anything beyond the colon */
  if (parts_requested & GET_PROTOCOL_PART) {
    if(colon) {
      char val = *(colon+1);
      *(colon+1) = '\0';
      StrAllocCopy(rv, url);
      *(colon+1) = val;
      /* If the user wants more url info, tack on extra slashes. */
      if( (parts_requested & GET_HOST_PART)
          || (parts_requested & GET_USERNAME_PART)
          || (parts_requested & GET_PASSWORD_PART)) {
        if( *(colon+1) == '/' && *(colon+2) == '/') {
          StrAllocCat(rv, "//");
        }
        /* If there's a third slash consider it file:/// and tack on the last slash. */
        if( *(colon+3) == '/' ) {
          StrAllocCat(rv, "/");
        }
      }
    }
  }

  /* Get the username if one exists */
  if (parts_requested & GET_USERNAME_PART) {
    if (colon && (*(colon+1) == '/') && (*(colon+2) == '/') && (*(colon+3) != '\0')) {
      if ( (slash = PL_strchr(colon+3, '/')) != NULL) {
        *slash = '\0';
      }
      if ( (atSign = PL_strchr(colon+3, '@')) != NULL) {
        *atSign = '\0';
        if ( (passwordColon = PL_strchr(colon+3, ':')) != NULL) {
          *passwordColon = '\0';
        }
        StrAllocCat(rv, colon+3);

        /* Get the password if one exists */
        if (parts_requested & GET_PASSWORD_PART) {
          if (passwordColon) {
            StrAllocCat(rv, ":");
            StrAllocCat(rv, passwordColon+1);
          }
        }
        if (parts_requested & GET_HOST_PART) {
          StrAllocCat(rv, "@");
        }
        if (passwordColon) {
          *passwordColon = ':';
        }
        *atSign = '@';
      }
      if (slash) {
        *slash = '/';
      }
    }
  }

  /* Get the host part */
  if (parts_requested & GET_HOST_PART) {
    if(colon) {
      if(*(colon+1) == '/' && *(colon+2) == '/') {
        slash = PL_strchr(colon+3, '/');
        if(slash) {
          *slash = '\0';
        }
        if( (atSign = PL_strchr(colon+3, '@')) != NULL) {
          host = atSign+1;
        } else {
          host = colon+3;
        }
        ques_mark = PL_strchr(host, '?');
        if(ques_mark) {
          *ques_mark = '\0';
        }
        gtThan = PL_strchr(host, '>');
        if (gtThan) {
          *gtThan = '\0';
        }

        /*
         * Protect systems whose header files forgot to let the client know when
         * gethostbyname would trash their stack.
         */
#ifndef MAX_HOST_NAME_LEN
#ifdef XP_OS2
#error Managed to get into cookie_ParseURL without defining MAX_HOST_NAME_LEN !!!
#endif
#endif
        /* limit hostnames to within MAX_HOST_NAME_LEN characters to keep from crashing */
        if(PL_strlen(host) > MAX_HOST_NAME_LEN) {
          char * cp;
          char old_char;
          cp = host+MAX_HOST_NAME_LEN;
          old_char = *cp;
          *cp = '\0';
          StrAllocCat(rv, host);
          *cp = old_char;
        } else {
          StrAllocCat(rv, host);
        }
        if(slash) {
          *slash = '/';
        }
        if(ques_mark) {
          *ques_mark = '?';
        }
        if (gtThan) {
          *gtThan = '>';
        }
      }
    }
  }

  /* Get the path part */
  if (parts_requested & GET_PATH_PART) {
    if(colon) {
      if(*(colon+1) == '/' && *(colon+2) == '/') {
        /* skip host part */
        slash = PL_strchr(colon+3, '/');
      } else {
        /* path is right after the colon */
        slash = colon+1;
      }
      if(slash) {
        ques_mark = PL_strchr(slash, '?');
        hash_mark = PL_strchr(slash, '#');
        if(ques_mark) {
          *ques_mark = '\0';
        }
        if(hash_mark) {
          *hash_mark = '\0';
        }
        StrAllocCat(rv, slash);
        if(ques_mark) { 
          *ques_mark = '?';
        }
        if(hash_mark) {
          *hash_mark = '#';
        }
      }
    }
  }

  if(parts_requested & GET_HASH_PART) {
    hash_mark = PL_strchr(url, '#'); /* returns a const char * */
    if(hash_mark) {
      ques_mark = PL_strchr(hash_mark, '?');
      if(ques_mark) {
        *ques_mark = '\0';
      }
      StrAllocCat(rv, hash_mark);
      if(ques_mark) {
        *ques_mark = '?';
      }
    }
  }

  if(parts_requested & GET_SEARCH_PART) {
    ques_mark = PL_strchr(url, '?'); /* returns a const char * */
    if(ques_mark) {
      hash_mark = PL_strchr(ques_mark, '#');
      if(hash_mark) {
        *hash_mark = '\0';
      }
      StrAllocCat(rv, ques_mark);
      if(hash_mark) {
        *hash_mark = '#';
      }
    }
  }

  /* copy in a null string if nothing was copied in */
  if(!rv) {
    StrAllocCopy(rv, "");
  }
  /* XP_OS2_FIX IBM-MAS: long URLS blowout tracemsg buffer, set max to 1900 chars */
  // TRACEMSG(("mkparse: ParseURL: parsed - %-.1900s",rv));
  return rv;
}
