/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */
/*
 *
 * Designed and Implemented by Lou Montulli '94
 * Heavily modified by Judson Valeski '97
 * Yada yada yada... Gagan Saksena '98
 *
 * This file implements HTTP access authorization
 * and HTTP cookies
 */
#include "mkutils.h"
#include "mkparse.h"
#include "mkaccess.h"
#include "prefapi.h"
#include "shist.h"
#include "jscookie.h"

#include "secnav.h"
#include "libevent.h"
#include "pwcacapi.h"

/* for XP_GetString() */
#include "xpgetstr.h"
extern int XP_CONFIRM_AUTHORIZATION_FAIL;
extern int XP_ACCESS_ENTER_USERNAME;
extern int XP_ACCESS_ENTER_USERNAME;
extern int XP_CONFIRM_PROXYAUTHOR_FAIL;
extern int XP_CONNECT_PLEASE_ENTER_PASSWORD_FOR_HOST;
extern int XP_PROXY_REQUIRES_UNSUPPORTED_AUTH_SCHEME;
extern int XP_LOOPING_OLD_NONCES;
extern int XP_UNIDENTIFIED_PROXY_SERVER;
extern int XP_PROXY_AUTH_REQUIRED_FOR;
extern int XP_CONNECT_PLEASE_ENTER_PASSWORD_FOR_PROXY;
extern int XP_FORTEZZA_PROXY_AUTH;
extern int MK_ACCESS_COOKIES_THE_SERVER;
extern int MK_ACCESS_COOKIES_WISHES; 
extern int MK_ACCESS_COOKIES_TOANYSERV; 
extern int MK_ACCESS_COOKIES_TOSELF;
extern int MK_ACCESS_COOKIES_NAME_AND_VAL;
extern int MK_ACCESS_COOKIES_COOKIE_WILL_PERSIST;
extern int MK_ACCESS_COOKIES_SET_IT;
extern int MK_ACCESS_YOUR_COOKIES;
extern int MK_ACCESS_MAXIMUM_COOKS;
extern int MK_ACCESS_COOK_COUNT;
extern int MK_ACCESS_MAXIMUM_COOKS_PER_SERV;
extern int MK_ACCESS_MAXIMUM_COOK_SIZE;
extern int MK_ACCESS_NO_COOKIES;
extern int MK_ACCESS_NAME;
extern int MK_ACCESS_VALUE;
extern int MK_ACCESS_HOST;
extern int MK_ACCESS_SEND_TO_HOST;
extern int MK_ACCESS_IS_DOMAIN;
extern int MK_ACCESS_IS_NOT_DOMAIN;
extern int MK_ACCESS_SEND_TO_PATH;
extern int MK_ACCESS_AND_BELOW;
extern int MK_ACCESS_SECURE;
extern int MK_ACCESS_EXPIRES;
extern int MK_ACCESS_END_OF_SESSION;

#define MAX_NUMBER_OF_COOKIES  300
#define MAX_COOKIES_PER_SERVER 20
#define MAX_BYTES_PER_COOKIE   4096  /* must be at least 1 */

/*
 * Authentication information for servers and proxies is kept
 * on separate lists, but both lists consist of net_AuthStruct's.
 */

PRIVATE XP_List * net_auth_list = NULL;
PRIVATE XP_List * net_proxy_auth_list = NULL;

PRIVATE Bool cookies_changed = FALSE;

PRIVATE NET_CookieBehaviorEnum net_CookieBehavior = NET_Accept;
PRIVATE Bool net_WarnAboutCookies = FALSE;
PRIVATE char *net_scriptName = (char *)0;

static const char *pref_cookieBehavior = "network.cookie.cookieBehavior";
static const char *pref_warnAboutCookies = "network.cookie.warnAboutCookies";
static const char *pref_scriptName = "network.cookie.filterName";


/*
 * Different schemes supported by the client.
 * Order means the order of preference between authentication schemes.
 *
 */
typedef enum _net_AuthType {
    AUTH_INVALID   = 0,
    AUTH_BASIC     = 1
#ifdef SIMPLE_MD5
	, AUTH_SIMPLEMD5 = 2		/* Much better than "Basic" */
#endif /* SIMPLE_MD5 */
    ,AUTH_FORTEZZA  = 3
} net_AuthType;


/*
 * This struct describes both Basic and SimpleMD5 authentication stuff,
 * for both HTTP servers and proxies.
 *
 */
typedef struct _net_AuthStruct {
    net_AuthType	auth_type;
    char *			path;			/* For normal authentication only */
	char *			proxy_addr;		/* For proxy authentication only */
	char *			username;		/* Obvious */
	char *			password;		/* Not too cryptic either */
	char *			auth_string;	/* Storage for latest Authorization str */
	char *			realm;			/* For all auth schemes */
#ifdef SIMPLE_MD5
    char *			domain;			/* SimpleMD5 only */
    char *			nonce;			/* SimpleMD5 only */
    char *			opaque;			/* SimpleMD5 only */
    XP_Bool			oldNonce;		/* SimpleMD5 only */
	int				oldNonce_retries;
#endif
    char *                      challenge;
    char *                      certChain;
    char *                      signature;
    char *                      clientRan;
    XP_Bool                     oldChallenge;
    int                         oldChallenge_retries;
} net_AuthStruct;



/*----------------- Normal client-server authentication ------------------ */

PRIVATE net_AuthStruct *
net_CheckForAuthorization(char * address, Bool exact_match)
{

    XP_List * list_ptr = net_auth_list;
    net_AuthStruct * auth_s;

	TRACEMSG(("net_CheckForAuthorization: checking for auth on: %s", address));

    while((auth_s = (net_AuthStruct *) XP_ListNextObject(list_ptr))!=0)
      {
		if(exact_match)
		  {
		    if(!XP_STRCMP(address, auth_s->path))
			    return(auth_s);
		  }
		else
		  {
			/* shorter strings always come last so there can be no
			 * ambiquity
			 */
		    if(!strncasecomp(address, auth_s->path, XP_STRLEN(auth_s->path)))
			    return(auth_s);
		  }
      }
   
    return(NULL);
}

/* returns TRUE if authorization is required
 */
PUBLIC Bool
NET_AuthorizationRequired(char * address)
{
    net_AuthStruct * rv;
	char * last_slash = XP_STRRCHR(address, '/');

	if(last_slash)
		*last_slash = '\0';

	rv = net_CheckForAuthorization(address, FALSE);

	if(last_slash)
		*last_slash = '/';

	if(!rv)
		return(FALSE);
	else
		return(TRUE);
}

/* returns a authorization string if one is required, otherwise
 * returns NULL
 */
PUBLIC char *
NET_BuildAuthString(MWContext * context, URL_Struct *URL_s)
{
	char * address = URL_s->address;
	net_AuthStruct * auth_s = net_CheckForAuthorization(address, FALSE);

	if(!auth_s)
#if defined(XP_WIN) && defined(MOZILLA_CLIENT)
		return(WFE_BuildCompuserveAuthString(URL_s));
#else
		return(NULL);
#endif
	else
	  {
	  	static char * auth_header = 0;
		
		if(auth_header)
			XP_FREE(auth_header);
		auth_header = PR_smprintf("Authorization: Basic %s"CRLF, auth_s->auth_string);
		return(auth_header);
	  }
}

PRIVATE net_AuthStruct *
net_ScanForHostnameRealmMatch(char * address, char * realm)
{
	char * proto_host = NET_ParseURL(address, GET_HOST_PART | GET_PROTOCOL_PART);
    XP_List * list_ptr = net_auth_list;
    net_AuthStruct * auth_s;

    while((auth_s = (net_AuthStruct *) XP_ListNextObject(list_ptr))!=0)
      {
        /* shorter strings always come last so there can be no
         * ambiquity
         */
        if(!strncasecomp(proto_host, auth_s->path, XP_STRLEN(proto_host))
			&& !strcasecomp(realm, auth_s->realm))
		{
			XP_FREE(proto_host);
            return(auth_s);
		}
      }
	XP_FREE(proto_host);
    return(NULL);
}

PRIVATE void
net_free_auth_struct(net_AuthStruct *auth)
{
    FREE(auth->path);
    FREE(auth->proxy_addr);
    FREE(auth->username);
    FREE(auth->password);
    FREE(auth->auth_string);
    FREE(auth->realm);
    /*FORTEZZA related stuff   */
    FREEIF(auth->challenge);
    FREEIF(auth->certChain);
    FREEIF(auth->signature);
    FREEIF(auth->clientRan);
    /*End FORTEZZA related stuff*/
    FREE(auth);
}

/* blows away all authorization structs currently in the list, and the list itself.
 * frees and nulls the list pointer itself (net_auth_list)
 */
PUBLIC void
NET_RemoveAllAuthorizations()
{
	net_AuthStruct * victim;
	if(XP_ListIsEmpty(net_auth_list)) /* XP_ListIsEmpty handles null list */
		return;

    while((victim = (net_AuthStruct *) XP_ListNextObject(net_auth_list)) != 0)
		net_free_auth_struct(victim);
	XP_ListDestroy(net_auth_list);
	net_auth_list = NULL;
}

PRIVATE void
net_remove_exact_auth_match_on_cancel(net_AuthStruct *prev_auth, char *cur_path)
{
	if(!prev_auth || !cur_path)
		return;

	if(!XP_STRCMP(prev_auth->path, cur_path))
      {
        /* if the paths are exact and the user cancels
         * remove the mapping
         */
        XP_ListRemoveObject(net_auth_list, prev_auth);
		net_free_auth_struct(prev_auth);
	  }
}

#define HTTP_PW_MODULE_NAME  "http_pw"
#define HTTP_PW_NAME_TOKEN            "name"
#define HTTP_PW_PASSWORD_TOKEN        "pass"

PRIVATE char *
gen_http_key(char *address, char *realm)
{
	char *rv=NULL;

	StrAllocCopy(rv, address);
	StrAllocCat(rv, "\t");
	StrAllocCat(rv, realm);

	return rv;
}

PRIVATE void
separate_http_key(char *key, char **address, char **realm)
{
	char *tab;

	*address = NULL;
	*realm = NULL;

	if(!key)
		return;

	tab = XP_STRCHR(key, '\t');

	if(!tab)
		return;

	*address = key;
	*realm = tab+1;
}

PRIVATE void
net_store_http_password(char *address, char *realm, char *username, char *password)
{
	char *key;
	PCNameValueArray *array = PC_NewNameValueArray();

	if(!array)
		return;

	PC_AddToNameValueArray(array, HTTP_PW_NAME_TOKEN, username);	
	PC_AddToNameValueArray(array, HTTP_PW_PASSWORD_TOKEN, password);	

	key = gen_http_key(address, realm);

	if(!key)
		return;

	PC_StorePasswordNameValueArray(HTTP_PW_MODULE_NAME, key, array);

	FREE(key);

}

PRIVATE void
net_remove_stored_http_password(char *url)
{
	PC_DeleteStoredPassword(HTTP_PW_MODULE_NAME, url);
}

MODULE_PRIVATE void
net_http_password_data_interp(
        char *module,
        char *key,
        char *data, int32 data_len,
        char *type_buffer, int32 type_buffer_size,
        char *url_buffer, int32 url_buffer_size,
        char *username_buffer, int32 username_buffer_size,
        char *password_buffer, int32 password_buffer_size)
{
	PCNameValueArray * array;
	char *username, *password;
	char *address, *realm;

	array = PC_CharToNameValueArray(data, data_len);

	if(!array)
		return;

	username = PC_FindInNameValueArray(array, HTTP_PW_NAME_TOKEN);
	password = PC_FindInNameValueArray(array, HTTP_PW_PASSWORD_TOKEN);
	
	XP_STRNCPY_SAFE(type_buffer, "HTTP basic authorization", type_buffer_size);

	separate_http_key(key, &address, &realm);

	if(address)
	{
		XP_STRNCPY_SAFE(url_buffer, address, url_buffer_size);
	}

	if(username)
	{
		XP_STRNCPY_SAFE(username_buffer, username, username_buffer_size);
		XP_FREE(username);
	}
	
	if(password)
	{
		XP_STRNCPY_SAFE(password_buffer, password, password_buffer_size);
		XP_FREE(password);
	}
}

PRIVATE void
net_initialize_http_access()
{
	/* register PW cache interp function */
	
	PC_RegisterDataInterpretFunc(HTTP_PW_MODULE_NAME, 	
				     net_http_password_data_interp);
}

/* returns false if the user wishes to cancel authorization
 * and TRUE if the user wants to continue with a new authorization
 * string
 */
/* HARDTS: I took a whack at fixing up some of the strings leaked in this 
 * function.  All the XP_FREEIF()s are new. 
 */
PUBLIC Bool 
NET_AskForAuthString(MWContext *context,
					 URL_Struct * URL_s, 
					 char * authenticate, 
					 char * prot_template,
					 Bool   already_sent_auth)
{
	static XP_Bool first_time=TRUE;
	net_AuthStruct *prev_auth;
	char *address=URL_s->address;
	char *host=NET_ParseURL(address, GET_HOST_PART);
	char *new_address=NULL;
	char *username=NULL,*colon=NULL,*password=NULL,*unamePwd=NULL;
	char *u_pass_string=NULL;
	char *auth_string=NULL;
	char *realm;
	char *slash;
	char *authenticate_header_value;
	char *buf=NULL;
	int32 len=0;
	int status;
	XP_Bool re_authorize=FALSE;

	TRACEMSG(("Entering NET_AskForAuthString"));

	if(first_time)
	{
		net_initialize_http_access();
		first_time = FALSE;
	}

	if(authenticate)
	  {
		/* check for the compuserve Remote-Passphrase type of
	 	 * authentication
	 	 */
		authenticate_header_value = XP_StripLine(authenticate);

#define COMPUSERVE_HEADER_NAME "Remote-Passphrase"

		if(!strncasecomp(authenticate_header_value, 
					 COMPUSERVE_HEADER_NAME, 
					 sizeof(COMPUSERVE_HEADER_NAME) - 1))
	  	{
	  		/* This is a compuserv style header 
	  	 	*/

		XP_FREEIF(host);
#if defined(XP_WIN) && defined(MOZILLA_CLIENT)
		return(WFE_DoCompuserveAuthenticate(context, URL_s, authenticate_header_value));
#else
		return(NET_AUTH_FAILED_DISPLAY_DOCUMENT);		
#endif	
	  	}			 
#define HTTP_BASIC_AUTH_TOKEN "BASIC"
		else if(strncasecomp(authenticate_header_value, 
					 HTTP_BASIC_AUTH_TOKEN, 
					 sizeof(HTTP_BASIC_AUTH_TOKEN) - 1))
		{
			/* unsupported auth type */
			return(NET_AUTH_FAILED_DISPLAY_DOCUMENT);		
		}
	  }

	new_address = NET_ParseURL(address,	GET_PROTOCOL_PART | GET_HOST_PART | GET_PATH_PART);
	if(!new_address) {
		XP_FREEIF(host);
		return NET_AUTH_FAILED_DISPLAY_DOCUMENT;
	}

	unamePwd=NET_ParseURL(address, GET_USERNAME_PART | GET_PASSWORD_PART);
	/* get the username & password out of the combo string */
	if( (colon = XP_STRCHR(unamePwd, ':')) != NULL ) {
		*colon='\0';
		username=XP_STRDUP(unamePwd);
		password=XP_STRDUP(colon+1);
		*colon=':';
		XP_FREE(unamePwd);
	} else {
		username=unamePwd;
	}

	if(username && !(*username) )
		FREE_AND_CLEAR(username);
	if(password && !(*password) )
		FREE_AND_CLEAR(password);

	/*if last char is not a slash then */
	if (new_address[XP_STRLEN(new_address)-1] != '/')
	{
		/* remove everything after the last slash */
		slash = XP_STRRCHR(new_address, '/');
		if(++slash)
			*slash = '\0';
	}

	if(!authenticate)
	  {
		realm = "unknown";
	  }
	else
	  {
		realm = XP_STRCHR(authenticate, '"');
	
		if(realm)
		  {
			realm++;

			/* terminate at next quote */
			XP_STRTOK(realm, "\"");

#define MAX_REALM_SIZE 128
			if(XP_STRLEN(realm) > MAX_REALM_SIZE)
				realm[MAX_REALM_SIZE] = '\0';
	
      }
		else
	      {
		    realm = "unknown";
	 	  }
		
    }

	/* no hostname/realm match search for exact match */
    prev_auth = net_CheckForAuthorization(new_address, FALSE);

    if(prev_auth && !already_sent_auth)
      {
		/* somehow the mapping changed since the time we sent
		 * the authorization.
		 * This happens sometimes because of the parrallel
		 * nature of the requests.
		 * In this case we want to just retry the connection
		 * since it will probably succede now.
		 */
		XP_FREEIF(host);
		XP_FREEIF(new_address);
		XP_FREEIF(username);
		XP_FREEIF(password);
		return(NET_RETRY_WITH_AUTH);
      }
    else if(prev_auth)
      {
		/* we sent the authorization string and it was wrong
		 */
        if(!FE_Confirm(context, XP_GetString(XP_CONFIRM_AUTHORIZATION_FAIL)))
		  {
        	TRACEMSG(("User canceled login!!!"));

			if(!XP_STRCMP(prev_auth->path, new_address))
			  {
				/* if the paths are exact and the user cancels
				 * remove the mapping
				 */
				net_remove_exact_auth_match_on_cancel(prev_auth, new_address);
				XP_FREEIF(host);
				XP_FREEIF(new_address);
				XP_FREEIF(username);
				XP_FREEIF(password);
            	return(NET_AUTH_FAILED_DISPLAY_DOCUMENT);
			  }
		  }
		
		if (!username)
			username = XP_STRDUP(prev_auth->username);
		if (!password)
			password = XP_STRDUP(prev_auth->password);
		re_authorize = TRUE;
      }
	else
	  {
		char *ptr1, *ptr2;

        /* scan all the authorization strings to see if we
         * can find a hostname and realm match.  If we find
         * a match then reduce the authorization path to include
         * the current path as well.
         */
        prev_auth = net_ScanForHostnameRealmMatch(address, realm);
    
        if(prev_auth)
          {
			char *tmp;

			net_remove_stored_http_password(prev_auth->path);

            /* compare the two url paths until they deviate
             * once they deviate truncate
             */
            for(ptr1 = prev_auth->path, ptr2 = new_address; *ptr1 && *ptr2; ptr1++, ptr2++)
			  {
                if(*ptr1 != *ptr2)
                  {
					break;        /* end for */
                  }
			  }
			/* truncate at *ptr1 now since the new address may
			 * be just a subpath of the original address and
			 * the compare above will not handle the subpath correctly
			 */
			*ptr1 = '\0'; /* truncate */

			if(*(ptr1-1) == '/')
				*(ptr1-1) = '\0'; /* strip a trailing slash */

			/* make sure a path always has at least a slash
			 * at the end.
			 * If the slash isn't there then
			 * the password will be sent to ports on the
			 * same host since we use a first part match
			 */
			tmp = NET_ParseURL(prev_auth->path, GET_PATH_PART);
			if(!*tmp)
				StrAllocCat(prev_auth->path, "/");
			FREE(tmp);				

			TRACEMSG(("Truncated new auth path to be: %s", prev_auth->path));

			net_store_http_password(prev_auth->path, prev_auth->realm, prev_auth->username, prev_auth->password);

			FREE(host);
			FREE(new_address);
			return(NET_RETRY_WITH_AUTH);
          }
	  }
					 
	/* Use username and/or password specified in URL_struct if exists. */
	if (!username && URL_s->username && *URL_s->username) {
		username = XP_STRDUP(URL_s->username);
	}
	if (!password && URL_s->password && *URL_s->password) {
		password = XP_STRDUP(URL_s->password);
	}

	if(!username && !password)
	{
		/* look for a previously stored password in the pw cache */
		PCNameValueArray *array;

		array = PC_CheckForStoredPasswordArray(HTTP_PW_MODULE_NAME, URL_s->address);

		if(array)
		{
			username = PC_FindInNameValueArray(array, HTTP_PW_NAME_TOKEN);
			password = PC_FindInNameValueArray(array, HTTP_PW_PASSWORD_TOKEN);

			if(!username)
				FREE_AND_CLEAR(password);
		}
	}

	/* if the password is filled in then the username must
	 * be filled in already.  
	 */
	if(!password || re_authorize)
	  {
		XP_Bool remember_password;
	   	host = NET_ParseURL(address, GET_HOST_PART);

		/* malloc memory here to prevent buffer overflow */
		len = XP_STRLEN(XP_GetString(XP_ACCESS_ENTER_USERNAME));
		len += XP_STRLEN(realm) + XP_STRLEN(host) + 10;
		
		buf = (char *)XP_ALLOC(len*sizeof(char));
		
		if(buf)
		  {
			PR_snprintf( buf, len*sizeof(char), 
						XP_GetString(XP_ACCESS_ENTER_USERNAME), 
						realm, host);


			NET_Progress(context, XP_GetString( XP_CONNECT_PLEASE_ENTER_PASSWORD_FOR_HOST) );
			if (username && !(*username))
				XP_FREE(username);
			XP_FREEIF(password);
			status = PC_PromptUsernameAndPassword(context, buf, 
											  	  &username, &password, 
												  &remember_password,
												  NET_IsURLSecure(URL_s->address));
	
			FREE(buf);
		  }
		else
		  {
			status = 0;
		  }

		FREE(host);

		if(!status)
		  {
			TRACEMSG(("User canceled login!!!"));

			/* if the paths are exact and the user cancels
			 * remove the mapping
			 */
			net_remove_exact_auth_match_on_cancel(prev_auth, new_address);

			XP_FREEIF(username);
			XP_FREEIF(password);
			XP_FREEIF(new_address);
			return(NET_AUTH_FAILED_DISPLAY_DOCUMENT);
		  }
		else if(!username || !password)
		  {
			XP_FREEIF(username);
			XP_FREEIF(password);
			XP_FREEIF(new_address);
			return(NET_AUTH_FAILED_DISPLAY_DOCUMENT);
		  }
		else if(remember_password)
		{
			net_store_http_password(URL_s->address, realm, username, password);
		}
	  }

	StrAllocCopy(u_pass_string, username);
	StrAllocCat(u_pass_string, ":");
	StrAllocCat(u_pass_string, password);

	len = XP_STRLEN(u_pass_string);
	auth_string = (char*) XP_ALLOC((((len+1)*4)/3)+10);

	if(!auth_string)
	  {
		XP_FREEIF(username);
		XP_FREEIF(password);
		XP_FREEIF(u_pass_string);
		FREE(new_address);
		return(NET_RETRY_WITH_AUTH);
	  }

	NET_UUEncode((unsigned char *)u_pass_string, (unsigned char*) auth_string, len);

	FREE(u_pass_string);

	if(prev_auth)
	  {
	    XP_FREEIF(prev_auth->auth_string);
        prev_auth->auth_string = auth_string;
		XP_FREEIF(prev_auth->username);
		prev_auth->username = username;
        XP_FREEIF(prev_auth->password);
        prev_auth->password = password;
	  }
	else
	  {
		XP_List * list_ptr = net_auth_list;
		net_AuthStruct * tmp_auth_ptr;
		size_t new_len;

		/* construct a new auth_struct
		 */
		prev_auth = XP_NEW_ZAP(net_AuthStruct);
	    if(!prev_auth)
		  {
			XP_FREEIF(auth_string);
			XP_FREEIF(username);
			XP_FREEIF(password);
		    FREE(new_address);
		    return(NET_RETRY_WITH_AUTH);
		  }
		
        prev_auth->auth_string = auth_string;
		prev_auth->username = username;
        prev_auth->password = password;
		prev_auth->path = 0;
		StrAllocCopy(prev_auth->path, new_address);
		prev_auth->realm = 0;
		StrAllocCopy(prev_auth->realm, realm);

		if(!net_auth_list)
		  {
			net_auth_list = XP_ListNew();
		    if(!net_auth_list)
			  {
          /* Maybe should free prev_auth here. */
		   		FREE(new_address);
				return(NET_RETRY_WITH_AUTH);
			  }
		  }		

		/* add it to the list so that it is before any strings of
		 * smaller length
		 */
		new_len = XP_STRLEN(prev_auth->path);
		while((tmp_auth_ptr = (net_AuthStruct *) XP_ListNextObject(list_ptr))!=0)
		  { 
			if(new_len > XP_STRLEN(tmp_auth_ptr->path))
			  {
				XP_ListInsertObject(net_auth_list, tmp_auth_ptr, prev_auth);
		   		FREE(new_address);
				return(NET_RETRY_WITH_AUTH);
			  }
		  }
		/* no shorter strings found in list */	
		XP_ListAddObjectToEnd(net_auth_list, prev_auth);
	  }

	FREE(new_address);
	return(NET_RETRY_WITH_AUTH);
}

/*--------------------------------------------------
 * The following routines support the 
 * Set-Cookie: / Cookie: headers
 */

PRIVATE XP_List * net_cookie_list=0;

typedef struct _net_CookieStruct {
    char * path;
	char * host;
	char * name;
    char * cookie;
	time_t expires;
	time_t last_accessed;
	Bool   secure;      /* only send for https connections */
	Bool   is_domain;   /* is it a domain instead of an absolute host? */
} net_CookieStruct;

/* Routines and data to protect the cookie list so it
**   can be accessed by mulitple threads
*/

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

static PRMonitor * cookie_lock_monitor = NULL;
static PRThread  * cookie_lock_owner = NULL;
static int cookie_lock_count = 0;

PRIVATE void
net_lock_cookie_list()
{
    if(!cookie_lock_monitor)
	cookie_lock_monitor = PR_NewNamedMonitor("cookie-lock");

    PR_EnterMonitor(cookie_lock_monitor);

    while(TRUE) {

	/* no current owner or owned by this thread */
	PRThread * t = PR_CurrentThread();
	if(cookie_lock_owner == NULL || cookie_lock_owner == t) {
	    cookie_lock_owner = t;
	    cookie_lock_count++;

	    PR_ExitMonitor(cookie_lock_monitor);
	    return;
	}

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

    }
}

PRIVATE void
net_unlock_cookie_list()
{
   PR_EnterMonitor(cookie_lock_monitor);

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

    cookie_lock_count--;

    if(cookie_lock_count == 0) {
	cookie_lock_owner = NULL;
	PR_Notify(cookie_lock_monitor);
    }
    PR_ExitMonitor(cookie_lock_monitor);

}

/* This should only get called while holding the cookie-lock
**
*/
PRIVATE void
net_FreeCookie(net_CookieStruct * cookie)
{

	if(!cookie)
		return;

	XP_ListRemoveObject(net_cookie_list, cookie);

	XP_FREEIF(cookie->path);
	XP_FREEIF(cookie->host);
	XP_FREEIF(cookie->name);
	XP_FREEIF(cookie->cookie);

	FREE(cookie);

	cookies_changed = TRUE;
}



/* blows away all cookies currently in the list, then blows away the list itself
 * nulling it after it's free'd
 */
PUBLIC void
NET_RemoveAllCookies()
{
	net_CookieStruct * victim;
	XP_List * cookieList;

	/* check for NULL or empty list */
	net_lock_cookie_list();
	cookieList = net_cookie_list;
	if(XP_ListIsEmpty(cookieList)) {
		net_unlock_cookie_list();
		return;
	}

    while((victim = (net_CookieStruct *) XP_ListNextObject(cookieList)) != 0) {
		net_FreeCookie(victim);
		cookieList=net_cookie_list;
	}
	XP_ListDestroy(net_cookie_list);
	net_cookie_list = NULL;
	net_unlock_cookie_list();
}

PRIVATE void
net_remove_oldest_cookie(void)
{
    XP_List * list_ptr;
    net_CookieStruct * cookie_s;
    net_CookieStruct * oldest_cookie;

	net_lock_cookie_list();
	list_ptr = net_cookie_list;

	if(XP_ListIsEmpty(list_ptr)) {
		net_unlock_cookie_list();
		return;
	}

	oldest_cookie = (net_CookieStruct*) list_ptr->next->object;
	
    while((cookie_s = (net_CookieStruct *) XP_ListNextObject(list_ptr))!=0)
      {
		if(cookie_s->last_accessed < oldest_cookie->last_accessed)
			oldest_cookie = cookie_s;
	  }

	if(oldest_cookie)
	  {
		TRACEMSG(("Freeing cookie because global max cookies has been exceeded"));
	    net_FreeCookie(oldest_cookie);
	  }
	net_unlock_cookie_list();
}

/* Remove any expired cookies from memory 
** This routine should only be called while holding the cookie list lock
*/
PRIVATE void
net_remove_expired_cookies(void)
{
    XP_List * list_ptr = net_cookie_list;
    net_CookieStruct * cookie_s;
	time_t cur_time = time(NULL);

	if(XP_ListIsEmpty(list_ptr))
		return;

    while((cookie_s = (net_CookieStruct *) XP_ListNextObject(list_ptr))!=0)
      {
		/* 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) ) {
			net_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 net_FreeCookie struct, even
			 * though it's inefficient.
			 */
			list_ptr = net_cookie_list;
		}
	  }
}

/* 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
net_CheckForMaxCookiesFromHost(const char * cur_host)
{
    XP_List * list_ptr = net_cookie_list;
    net_CookieStruct * cookie_s;
    net_CookieStruct * oldest_cookie = 0;
	int cookie_count = 0;

    if(XP_ListIsEmpty(list_ptr))
        return;

    while((cookie_s = (net_CookieStruct *) XP_ListNextObject(list_ptr))!=0)
      {
	    if(!strcasecomp(cookie_s->host, cur_host))
		  {
			cookie_count++;
			if(!oldest_cookie 
				|| oldest_cookie->last_accessed > cookie_s->last_accessed)
                oldest_cookie = cookie_s;
		  }
      }

	if(cookie_count >= MAX_COOKIES_PER_SERVER && oldest_cookie)
	  {
		TRACEMSG(("Freeing cookie because max cookies per server has been exceeded"));
        net_FreeCookie(oldest_cookie);
	  }
}


/* search for previous exact match
** This routine should only be called while holding the cookie lock
*/
PRIVATE net_CookieStruct *
net_CheckForPrevCookie(char * path,
                   char * hostname,
                   char * name)
{

    XP_List * list_ptr = net_cookie_list;
    net_CookieStruct * cookie_s;

    while((cookie_s = (net_CookieStruct *) XP_ListNextObject(list_ptr))!=0)
      {
		if(path 
			&& hostname 
				&& cookie_s->path
				   && cookie_s->host
					  && cookie_s->name
					 	 && !XP_STRCMP(name, cookie_s->name)
							&& !XP_STRCMP(path, cookie_s->path)
								&& !XP_STRCASECMP(hostname, cookie_s->host))
                return(cookie_s);
			
      }

    return(NULL);
}

/* cookie utility functions */
PRIVATE void
NET_SetCookieBehaviorPref(NET_CookieBehaviorEnum x)
{
	net_CookieBehavior = x;

	if(net_CookieBehavior == NET_DontUse)
		XP_FileRemove("", xpHTTPCookie);
}

PRIVATE void
NET_SetCookieWarningPref(Bool x)
{
	net_WarnAboutCookies = x;
}

PRIVATE void
NET_SetCookieScriptPref(const char *name)
{
    XP_FREEIF(net_scriptName);
	if( name && *name )
		net_scriptName=XP_STRDUP(name);
	else
		net_scriptName=NULL;
}

PRIVATE NET_CookieBehaviorEnum
NET_GetCookieBehaviorPref(void)
{
	return net_CookieBehavior;
}

PRIVATE Bool
NET_GetCookieWarningPref(void)
{
	return net_WarnAboutCookies;
}

PRIVATE const char *
NET_GetCookieScriptPref(void)
{
    return (const char *)net_scriptName;
}

MODULE_PRIVATE int PR_CALLBACK
NET_CookieBehaviorPrefChanged(const char * newpref, void * data)
{
	int32	n;
	PREF_GetIntPref(pref_cookieBehavior, &n);
	NET_SetCookieBehaviorPref((NET_CookieBehaviorEnum)n);
	return PREF_NOERROR;
}

MODULE_PRIVATE int PR_CALLBACK
NET_CookieWarningPrefChanged(const char * newpref, void * data)
{
	Bool	x;
	PREF_GetBoolPref(pref_warnAboutCookies, &x);
	NET_SetCookieWarningPref(x);
	return PREF_NOERROR;
}

MODULE_PRIVATE int PR_CALLBACK
NET_CookieScriptPrefChanged(const char * newpref, void * data)
{
    char    s[64];
    int len = sizeof(s);
    PREF_GetCharPref(pref_scriptName, s, &len);
    NET_SetCookieScriptPref(s);
    return PREF_NOERROR;
}
 

/* called from mkgeturl.c, NET_InitNetLib(). This sets the module local cookie pref variables
   and registers the callbacks */
PUBLIC void
NET_RegisterCookiePrefCallbacks(void)
{
	int32	n;
	Bool	x;
    char    s[64];
    int len = sizeof(s);

	PREF_GetIntPref(pref_cookieBehavior, &n);
	NET_SetCookieBehaviorPref((NET_CookieBehaviorEnum)n);
	PREF_RegisterCallback(pref_cookieBehavior, NET_CookieBehaviorPrefChanged, NULL);

	PREF_GetBoolPref(pref_warnAboutCookies, &x);
	NET_SetCookieWarningPref(x);
	PREF_RegisterCallback(pref_warnAboutCookies, NET_CookieWarningPrefChanged, NULL);

	PREF_GetCharPref(pref_scriptName, s, &len);
	NET_SetCookieScriptPref(s);
	PREF_RegisterCallback(pref_scriptName, NET_CookieScriptPrefChanged, NULL);
}

/* returns TRUE if authorization is required
** 
**
** IMPORTANT:  Now that this routine is mutli-threaded it is up
**             to the caller to free any returned string
*/
PUBLIC char *
NET_GetCookie(MWContext * context, char * address)
{
	char *name=0;
	char *host = NET_ParseURL(address, GET_HOST_PART);
	char *path = NET_ParseURL(address, GET_PATH_PART);
    XP_List * list_ptr;
    net_CookieStruct * cookie_s;
	Bool first=TRUE;
	Bool secure_path=FALSE;
	time_t cur_time = time(NULL);
	int host_length;
	int domain_length;

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

	/* disable cookie's if the user's prefs say so
	 */
	if(NET_GetCookieBehaviorPref() == NET_DontUse)
		return NULL;

	if(!strncasecomp(address, "https", 5))
		secure_path = TRUE;

	/* search for all cookies
	 */
	net_lock_cookie_list();
	list_ptr = net_cookie_list;
    while((cookie_s = (net_CookieStruct *) XP_ListNextObject(list_ptr))!=0)
      {
		if(!cookie_s->host)
			continue;

		/* check the host or domain first
		 */
		if(cookie_s->is_domain)
		  {
			char *cp;
			domain_length = XP_STRLEN(cookie_s->host);

			/* 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(domain_length > host_length 
				|| strncasecomp(cookie_s->host, 
								&host[host_length - domain_length], 
								domain_length))
			  {
				/* no match. FAIL 
				 */
				continue;
			  }
			
		  }
		else if(strcasecomp(host, cookie_s->host))
		  {
			/* hostname matchup failed. FAIL
			 */
			continue;
		  }

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

			/* if the cookie is secure and the path isn't
			 * dont send it
			 */
			if(cookie_s->secure && !secure_path)
				continue;  /* back to top of while */
			
			/* check for expired cookies
			 */
			if( cookie_s->expires && (cookie_s->expires < cur_time) )
			  {
				/* expire and remove the cookie 
				 */
		   		net_FreeCookie(cookie_s);

				/* start the list parsing over :(
				 * we must also start the string over
				 */
				FREE_AND_CLEAR(rv);
				list_ptr = net_cookie_list;
				first = TRUE; /* reset first */
				continue;
			  }

			if(first)
				first = FALSE;
			else
				StrAllocCat(rv, "; ");
			
			if(cookie_s->name && *cookie_s->name != '\0')
			  {
				cookie_s->last_accessed = 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 || !XP_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);
			  }
          }
	  }

	  net_unlock_cookie_list();
	XP_FREEIF(name);
	FREE(path);
	FREE(host);

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

/* Java script is calling NET_SetCookieString, netlib is calling 
** this via NET_SetCookieStringFromHttp.
*/
PRIVATE void
net_IntSetCookieString(MWContext * context, 
					char * cur_url,
					char * set_cookie_header,
					time_t timeToExpire)
{
	net_CookieStruct * prev_cookie;
	char *path_from_header=NULL, *host_from_header=NULL;
	char *name_from_header=NULL, *cookie_from_header=NULL;
	time_t expires=0;
	char *cur_path = NET_ParseURL(cur_url, GET_PATH_PART);
	char *cur_host = NET_ParseURL(cur_url, GET_HOST_PART);
	char *semi_colon, *ptr, *equal;
	const char *script_name;
	XP_Bool set_secure=FALSE, is_domain=FALSE, ask=FALSE, accept=FALSE;
	MWContextType type;

	if(!context) {
		XP_FREE(cur_path);
		XP_FREE(cur_host);
		return;
	}

	/* Only allow cookies to be set in the listed contexts. We
	 * don't want cookies being set in html mail. 
	 */
	type = context->type;
	if(!( (type == MWContextBrowser)
		|| (type == MWContextHTMLHelp)
		|| (type == MWContextPane) )) {
		XP_FREE(cur_path);
		XP_FREE(cur_host);
		return;
	}
	
	if(NET_GetCookieBehaviorPref() == NET_DontUse) {
		XP_FREE(cur_path);
		XP_FREE(cur_host);
		return;
	}

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

	/* parse path and expires attributes from header if
 	 * present
	 */
	semi_colon = XP_STRCHR(set_cookie_header, ';');

	if(semi_colon)
	  {
		/* truncate at semi-colon and advance 
		 */
		*semi_colon++ = '\0';

		/* there must be some attributes. (hopefully)
		 */
		if(strcasestr(semi_colon, "secure"))
			set_secure = TRUE;

		/* look for the path attribute
		 */
		ptr = 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(XP_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 = 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(XP_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 = XP_STRCHR(domain_from_header, '.');
			if(dot)
                dot = XP_STRCHR(dot+1, '.');

			if(!dot || *(dot+1) == '\0') {
				/* did not pass two dot test. FAIL
				 */
				XP_FREE(domain_from_header);
				XP_FREE(cur_path);
				XP_FREE(cur_host);
				TRACEMSG(("DOMAIN failed two dot test"));
				return;
			  }

			/* strip port numbers from the current host
			 * for the domain test
			 */
			colon = XP_STRCHR(cur_host, ':');
			if(colon)
			   *colon = '\0';

			domain_length   = XP_STRLEN(domain_from_header);
			cur_host_length = XP_STRLEN(cur_host);

			/* check to see if the host is in the domain
			 */
			if(domain_length > cur_host_length
				|| strcasecomp(domain_from_header, 
							   &cur_host[cur_host_length-domain_length]))
			  {
				TRACEMSG(("DOMAIN failed host within domain test."
					  " Domain: %s, Host: %s", domain_from_header, cur_host));
				XP_FREE(domain_from_header);
				XP_FREE(cur_path);
				XP_FREE(cur_host);
				return;
              }

			/* all tests passed, copy in domain to hostname field
			 */
			StrAllocCopy(host_from_header, domain_from_header);
			is_domain = TRUE;

			TRACEMSG(("Accepted domain: %s", host_from_header));

			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 = 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)
				expires = timeToExpire;
			else
				expires = NET_ParseDate(date);

			TRACEMSG(("Have expires date: %ld", expires));
		  }
	  }

	if(!path_from_header) {
        /* strip down everything after the last slash
         * to get the path.
         */
        char * slash = XP_STRRCHR(cur_path, '/');
        if(slash)
            *slash = '\0';

		path_from_header = cur_path;
	  } else {
		XP_FREE(cur_path);
	  }

	if(!host_from_header)
		host_from_header = cur_host;
	else
		XP_FREE(cur_host);

	/* keep cookies under the max bytes limit */
	if(XP_STRLEN(set_cookie_header) > MAX_BYTES_PER_COOKIE)
		set_cookie_header[MAX_BYTES_PER_COOKIE-1] = '\0';

	/* separate the name from the cookie */
	equal = XP_STRCHR(set_cookie_header, '=');

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

    /* If there's a script, call it now */
    script_name = NET_GetCookieScriptPref();
    if( (const char *)0 != script_name ) {
        JSCFCookieData *cd;
        Bool changed = FALSE;
        JSCFResult result;

        cd = XP_NEW_ZAP(JSCFCookieData);
        if( (JSCFCookieData *)0 == cd ) {
            XP_FREEIF(path_from_header);
            XP_FREEIF(host_from_header);
            XP_FREEIF(name_from_header);
            XP_FREEIF(cookie_from_header);
            /* FREEIF(cur_path); */
            /* FREEIF(cur_host); */
            return;
        }

        cd->path_from_header = path_from_header;
        cd->host_from_header = host_from_header;
        cd->name_from_header = name_from_header;
        cd->cookie_from_header = cookie_from_header;
        cd->expires = expires;
        cd->url = cur_url;
        cd->secure = set_secure;
        cd->domain = is_domain;
        cd->prompt = NET_GetCookieWarningPref();
        cd->preference = NET_GetCookieBehaviorPref();

	/*
	 * This probably is only safe to do from the mozilla thread
	 *   since it might do file I/O and uses the preferences
	 *   global context + objects
	 * XXX chouck
	 */
        result = JSCF_Execute(context, script_name, cd, &changed);
		if( result != JSCF_error) {
			if( changed ) {
				if( cd->path_from_header != path_from_header ) {
					XP_FREEIF(path_from_header);
					path_from_header = XP_STRDUP(cd->path_from_header);
				}
				if( cd->host_from_header != host_from_header ) {
					XP_FREEIF(host_from_header);
					host_from_header = XP_STRDUP(cd->host_from_header);
				}
				if( cd->name_from_header != name_from_header ) {
					XP_FREEIF(name_from_header);
					name_from_header = XP_STRDUP(cd->name_from_header);
				}
				if( cd->cookie_from_header != cookie_from_header ) {
					XP_FREEIF(cookie_from_header);
				  cookie_from_header = XP_STRDUP(cd->cookie_from_header);
			  }
			  if( cd->expires != expires )
				  expires = cd->expires;
			  if( cd->domain != is_domain )
				  /* lets hope the luser remembered to change the domain field */
				  is_domain = cd->domain;
			  if( cd->secure != set_secure )
				  set_secure = cd->secure;
			}
			switch( result ) {
				case JSCF_reject:
					XP_FREEIF(path_from_header);
					XP_FREEIF(host_from_header);
					XP_FREEIF(name_from_header);
					XP_FREEIF(cookie_from_header);
					XP_FREE(cd);
					/* FREEIF(cur_path); */
					/* FREEIF(cur_host); */
				return;
				case JSCF_accept:
					accept=TRUE;
				case JSCF_error:
				case JSCF_ask:
					ask=TRUE;
				case JSCF_whatever:
				break;
			}
		}
		XP_FREE(cd);
    }
 

	if( (NET_GetCookieWarningPref() || ask) && !accept ) {
		/* the user wants to know about cookies so let them
		 * know about every one that is set and give them
		 * the choice to accept it or not
		 */
		char * new_string=0;
		char * tmp_host = NET_ParseURL(cur_url, GET_HOST_PART);

        StrAllocCopy(new_string, XP_GetString(MK_ACCESS_COOKIES_THE_SERVER));
        StrAllocCat(new_string, tmp_host ? tmp_host : "");
        StrAllocCat(new_string, XP_GetString(MK_ACCESS_COOKIES_WISHES));

		StrAllocCopy(new_string, XP_GetString(MK_ACCESS_COOKIES_THE_SERVER));
		StrAllocCat(new_string, tmp_host ? tmp_host : "");
		StrAllocCat(new_string, XP_GetString(MK_ACCESS_COOKIES_WISHES));

		XP_FREE(tmp_host);

		if(is_domain) {
			StrAllocCat(new_string, XP_GetString(MK_ACCESS_COOKIES_TOANYSERV));
			StrAllocCat(new_string, host_from_header);
		  } else {
			StrAllocCat(new_string, XP_GetString(MK_ACCESS_COOKIES_TOSELF));
		  }

		StrAllocCat(new_string, XP_GetString(MK_ACCESS_COOKIES_NAME_AND_VAL));

		StrAllocCat(new_string, name_from_header);
		StrAllocCat(new_string, "=");
		StrAllocCat(new_string, cookie_from_header);
		StrAllocCat(new_string, "\n");

		if(expires) {
			StrAllocCat(new_string, XP_GetString(MK_ACCESS_COOKIES_COOKIE_WILL_PERSIST));
			StrAllocCat(new_string, ctime(&expires));
		  }

		StrAllocCat(new_string, XP_GetString(MK_ACCESS_COOKIES_SET_IT));

		/* 
		 * Who knows what thread we are on.  Only the mozilla thread
		 *   is allowed to post dialogs so, if need be, go over there
		 */
		if(!ET_PostMessageBox(context, new_string, TRUE)) {
			XP_FREEIF(new_string);
			return;
		}
		XP_FREEIF(new_string);
	  }

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

	/* 
	 * 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
	 */
	net_lock_cookie_list();

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

	if(XP_ListCount(net_cookie_list) > MAX_NUMBER_OF_COOKIES-1)
		net_remove_oldest_cookie();


    prev_cookie = net_CheckForPrevCookie(path_from_header, 
								   		 host_from_header, 
								   		 name_from_header);

    if(prev_cookie) {
        prev_cookie->expires = expires;
        XP_FREEIF(prev_cookie->cookie);
        XP_FREEIF(prev_cookie->path);
        XP_FREEIF(prev_cookie->host);
        XP_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->secure = set_secure;
        prev_cookie->is_domain = is_domain;
		prev_cookie->last_accessed = time(NULL);
      }	else {
		XP_List * list_ptr = net_cookie_list;
		net_CookieStruct * tmp_cookie_ptr;
		size_t new_len;

        /* construct a new cookie_struct
         */
        prev_cookie = XP_NEW(net_CookieStruct);
        if(!prev_cookie) {
			XP_FREEIF(path_from_header);
			XP_FREEIF(host_from_header);
			XP_FREEIF(name_from_header);
			XP_FREEIF(cookie_from_header);
			net_unlock_cookie_list();
            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 = expires;
        prev_cookie->secure  = set_secure;
        prev_cookie->is_domain = is_domain;
		prev_cookie->last_accessed = time(NULL);

		if(!net_cookie_list) {
			net_cookie_list = XP_ListNew();
		    if(!net_cookie_list) {
				XP_FREEIF(path_from_header);
				XP_FREEIF(name_from_header);
				XP_FREEIF(host_from_header);
				XP_FREEIF(cookie_from_header);
				XP_FREE(prev_cookie);
				net_unlock_cookie_list();
				return;
			  }
		  }		

		/* add it to the list so that it is before any strings of
		 * smaller length
		 */
		new_len = XP_STRLEN(prev_cookie->path);
		while((tmp_cookie_ptr = (net_CookieStruct *) XP_ListNextObject(list_ptr))!=0) { 
			if(new_len > XP_STRLEN(tmp_cookie_ptr->path)) {
				XP_ListInsertObject(net_cookie_list, tmp_cookie_ptr, prev_cookie);
				net_unlock_cookie_list();
				cookies_changed = TRUE;
				return;
			  }
		  }
		/* no shorter strings found in list */	
		XP_ListAddObjectToEnd(net_cookie_list, prev_cookie);
	  }

	/* At this point we know a cookie has changed. Write the cookies to file. */
	cookies_changed = TRUE;
	NET_SaveCookies(NULL);
	net_unlock_cookie_list();
	return;
}

PUBLIC void
NET_SetCookieString(MWContext * context, 
					char * cur_url,
					char * set_cookie_header) {
	net_IntSetCookieString(context, cur_url, set_cookie_header, 0);
}

/* Determines whether the inlineHost is in the same domain as the currentHost. For use with rfc 2109
 * compliance/non-compliance. */
PRIVATE int
NET_SameDomain(char * currentHost, char * inlineHost)
{
	char * dot = 0;
	char * currentDomain = 0;
	char * inlineDomain = 0;

	if(!currentHost || !inlineHost)
		return 0;

	/* case insensitive compare */
	if(XP_STRCASECMP(currentHost, inlineHost) == 0)
		return 1;

	currentDomain = XP_STRCHR(currentHost, '.');
	inlineDomain = XP_STRCHR(inlineHost, '.');

	if(!currentDomain || !inlineDomain)
		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 inlineDomain is within the currentDomain */
	dot = XP_STRCHR(inlineDomain, '.');
	if(dot)
		dot = XP_STRCHR(dot+1, '.');
	else
		return 0;

	/* handle .com. case */
	if(!dot || (*(dot+1) == '\0') )
		return 0;

	if(!XP_STRCASECMP(inlineDomain, currentDomain))
		return 1;
	return 0;
}

/* This function wrapper wraps NET_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
NET_SetCookieStringFromHttp(FO_Present_Types outputFormat,
							URL_Struct * URL_s,
							MWContext * context, 
							char * cur_url,
							char * set_cookie_header)
{
	/* 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, *date=NULL;
	time_t gmtCookieExpires=0, expires=0;

	if(CLEAR_CACHE_BIT(outputFormat) != FO_PRESENT && CLEAR_CACHE_BIT(outputFormat) != FO_SAVE_AS)
	{
		if (NET_GetCookieBehaviorPref() == NET_DontAcceptForeign)
		{
			/* the user doesn't want foreign cookies, check to see if its foreign */
			char * curSessionHistHost = 0;
			char * theColon = 0;
			char * curHost = NET_ParseURL(cur_url, GET_HOST_PART);
			History_entry * shistEntry = SHIST_GetCurrent(&context->hist);
			if (shistEntry) {
			curSessionHistHost = NET_ParseURL(shistEntry->address, GET_HOST_PART);
			}
			if(!curHost || !curSessionHistHost)
			{
				XP_FREEIF(curHost);
				XP_FREEIF(curSessionHistHost);
				return;
			}

			/* strip ports */
			theColon = XP_STRCHR(curHost, ':');
			if(theColon)
			   *theColon = '\0';
			theColon = XP_STRCHR(curSessionHistHost, ':');
			if(theColon)
				*theColon = '\0';

			/* if it's foreign, get out of here after a little clean up */
			if(!NET_SameDomain(curHost, curSessionHistHost))
			{
				XP_FREEIF(curHost);	
				XP_FREEIF(curSessionHistHost);
				return;
			}
			XP_FREEIF(curHost);	
			XP_FREEIF(curSessionHistHost);
		}
	}

	/* 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 = strcasestr(set_cookie_header, "expires=");
	if(ptr)
	{
		char *date =  ptr+8;
		char origLast = '\0';
		for(ptr=date; *ptr != '\0'; ptr++)
			if(*ptr == ';')
			  {
				origLast = ';';
				*ptr = '\0';
				break;
			  }
		expires = NET_ParseDate(date);
		*ptr=origLast;
	}
	if( URL_s->server_date && expires )
	{
		/* If the cookie has expired, don't waste any more time. */
		if( expires < URL_s->server_date )
		{
			return;
		}
		else
		{
			gmtCookieExpires = expires - URL_s->server_date + time(NULL);
			/* if overflow */
			if( gmtCookieExpires < time(NULL) )
				gmtCookieExpires = (((unsigned) (~0) << 1) >> 1); /* max int */
		}
	}

	net_IntSetCookieString(context, cur_url, set_cookie_header, gmtCookieExpires);
}

/* saves out the HTTP cookies to disk
 *
 * on entry pass in the name of the file to save
 *
 * returns 0 on success -1 on failure.
 *
 */
PUBLIC int
NET_SaveCookies(char * filename)
{
    XP_List * list_ptr;
    net_CookieStruct * cookie_s;
	time_t cur_date = time(NULL);
	XP_File fp;
	int32 len = 0;
	char date_string[36];

	if(NET_GetCookieBehaviorPref() == NET_DontUse)
	  return(-1);

	if(!cookies_changed)
	  return(-1);

	net_lock_cookie_list();
	list_ptr = net_cookie_list;
	if(XP_ListIsEmpty(list_ptr)) {
		net_unlock_cookie_list();
		return(-1);
	}

	if(!(fp = XP_FileOpen(filename, xpHTTPCookie, XP_FILE_WRITE))) {
		net_unlock_cookie_list();
		return(-1);
	}

	len = XP_FileWrite("# Netscape HTTP Cookie File" LINEBREAK
				 "# http://www.netscape.com/newsref/std/cookie_spec.html"
				 LINEBREAK "# This is a generated file!  Do not edit."
				 LINEBREAK LINEBREAK,
				 -1, fp);
	if (len < 0)
	{
		XP_FileClose(fp);
		net_unlock_cookie_list();
		return -1;
	}

	/* format shall be:
 	 *
	 * host \t is_domain \t path \t secure \t expires \t name \t cookie
	 *
	 * is_domain is TRUE or FALSE
	 * secure is TRUE or FALSE  
	 * expires is a time_t integer
	 * cookie can have tabs
	 */
    while((cookie_s = (net_CookieStruct *) XP_ListNextObject(list_ptr)) != NULL)
      {
		if(cookie_s->expires < cur_date)
			continue;  /* don't write entry if cookie has expired 
						* or has no expiration date
						*/

		len = XP_FileWrite(cookie_s->host, -1, fp);
		if (len < 0)
		{
			XP_FileClose(fp);
			net_unlock_cookie_list();
			return -1;
		}
		XP_FileWrite("\t", 1, fp);
		
		if(cookie_s->is_domain)
			XP_FileWrite("TRUE", -1, fp);
		else
			XP_FileWrite("FALSE", -1, fp);
		XP_FileWrite("\t", 1, fp);

		XP_FileWrite(cookie_s->path, -1, fp);
		XP_FileWrite("\t", 1, fp);

		if(cookie_s->secure)
		    XP_FileWrite("TRUE", -1, fp);
		else
		    XP_FileWrite("FALSE", -1, fp);
		XP_FileWrite("\t", 1, fp);

		PR_snprintf(date_string, sizeof(date_string), "%lu", cookie_s->expires);
		XP_FileWrite(date_string, -1, fp);
		XP_FileWrite("\t", 1, fp);

		XP_FileWrite(cookie_s->name, -1, fp);
		XP_FileWrite("\t", 1, fp);

		XP_FileWrite(cookie_s->cookie, -1, fp);
 		len = XP_FileWrite(LINEBREAK, -1, fp);
		if (len < 0)
		{
			XP_FileClose(fp);
			net_unlock_cookie_list();
			return -1;
		}
      }

	cookies_changed = FALSE;

	XP_FileClose(fp);
	net_unlock_cookie_list();
    return(0);
}


/* reads HTTP cookies from disk
 *
 * on entry pass in the name of the file to read
 *
 * returns 0 on success -1 on failure.
 *
 *
 */
#define LINE_BUFFER_SIZE 4096

PUBLIC int
NET_ReadCookies(char * filename)
{
    XP_List * list_ptr;
    net_CookieStruct *new_cookie, *tmp_cookie_ptr;
	size_t new_len;
    XP_File fp;
	char buffer[LINE_BUFFER_SIZE];
	char *host, *is_domain, *path, *secure, *expires, *name, *cookie;
	Bool added_to_list;

    if(!(fp = XP_FileOpen(filename, xpHTTPCookie, XP_FILE_READ)))
        return(-1);

	net_lock_cookie_list();
	list_ptr = net_cookie_list;

    /* format is:
     *
     * host \t is_domain \t path \t secure \t expires \t name \t cookie
     *
	 * if this format isn't respected we move onto the next line in the file.
     * is_domain is TRUE or FALSE	-- defaulting to FALSE
     * secure is TRUE or FALSE   -- should default to TRUE
     * expires is a time_t integer
     * cookie can have tabs
     */
    while(XP_FileReadLine(buffer, LINE_BUFFER_SIZE, fp))
      {
		added_to_list = FALSE;

		if (*buffer == '#' || *buffer == CR || *buffer == LF || *buffer == 0)
		  continue;

		host = buffer;
		
		if( !(is_domain = XP_STRCHR(host, '\t')) )
			continue;
		*is_domain++ = '\0';
		if(*is_domain == CR || *is_domain == LF || *is_domain == 0)
			continue;
		
		if( !(path = XP_STRCHR(is_domain, '\t')) )
			continue;
		*path++ = '\0';
		if(*path == CR || *path == LF || *path == 0)
			continue;

		if( !(secure = XP_STRCHR(path, '\t')) )
			continue;
		*secure++ = '\0';
		if(*secure == CR || *secure == LF || *secure == 0)
			continue;

		if( !(expires = XP_STRCHR(secure, '\t')) )
			continue;
		*expires++ = '\0';
		if(*expires == CR || *expires == LF || *expires == 0)
			continue;

        if( !(name = XP_STRCHR(expires, '\t')) )
			continue;
		*name++ = '\0';
		if(*name == CR || *name == LF || *name == 0)
			continue;

        if( !(cookie = XP_STRCHR(name, '\t')) )
			continue;
		*cookie++ = '\0';
		if(*cookie == CR || *cookie == LF || *cookie == 0)
			continue;

		/* remove the '\n' from the end of the cookie */
		XP_StripLine(cookie);

        /* construct a new cookie_struct
         */
        new_cookie = XP_NEW(net_CookieStruct);
        if(!new_cookie)
          {
			net_unlock_cookie_list();
            return(-1);
          }

		XP_MEMSET(new_cookie, 0, sizeof(net_CookieStruct));
    
        /* copy
         */
        StrAllocCopy(new_cookie->cookie, cookie);
        StrAllocCopy(new_cookie->name, name);
        StrAllocCopy(new_cookie->path, path);
        StrAllocCopy(new_cookie->host, host);
        new_cookie->expires = atol(expires);
		if(!XP_STRCMP(secure, "FALSE"))
        	new_cookie->secure = FALSE;
		else
        	new_cookie->secure = TRUE;
        if(!XP_STRCMP(is_domain, "TRUE"))
        	new_cookie->is_domain = TRUE;
        else
        	new_cookie->is_domain = FALSE;

		if(!net_cookie_list)
		  {
			net_cookie_list = XP_ListNew();
		    if(!net_cookie_list)
			  {
				net_unlock_cookie_list();
				return(-1);
			  }
		  }		

		/* add it to the list so that it is before any strings of
		 * smaller length
		 */
		new_len = XP_STRLEN(new_cookie->path);
		while((tmp_cookie_ptr = (net_CookieStruct *) XP_ListNextObject(list_ptr)) != NULL)
		  { 
			if(new_len > XP_STRLEN(tmp_cookie_ptr->path))
			  {
				XP_ListInsertObject(net_cookie_list, tmp_cookie_ptr, new_cookie);
				added_to_list = TRUE;
				break;
			  }
		  }

		/* no shorter strings found in list */	
		if(!added_to_list)
		    XP_ListAddObjectToEnd(net_cookie_list, new_cookie);
	  }

    XP_FileClose(fp);
	net_unlock_cookie_list();

	cookies_changed = FALSE;

    return(0);
}



/* --- New stuff: General auth utils (currently used only by proxy auth) --- */

/*
 * Figure out the authentication scheme used; currently supported:
 *
 *		* Basic
 *		* SimpleMD5
 *
 */
PRIVATE net_AuthType
net_auth_type(char *name)
{
	if (name) {
		while (*name && XP_IS_SPACE(*name))
			name++;
		if (!strncasecomp(name, "basic", 5))
			return AUTH_BASIC;
#ifdef SIMPLE_MD5
		else if (!strncasecomp(name, "simplemd5", 9))
			return AUTH_SIMPLEMD5;
#endif
		/*FORTEZZA checks*/
		else if (!strncasecomp(name, "fortezzaproxy", 13))
			return AUTH_FORTEZZA;
	}
	return AUTH_INVALID;
}


/*
 * Figure out better of two {WWW,Proxy}-Authenticate headers;
 * SimpleMD5 is better than Basic.  Uses the order of AuthType
 * enum values.
 *
 */
MODULE_PRIVATE XP_Bool
net_IsBetterAuth(char *new_auth, char *old_auth)
{
	if (!old_auth || net_auth_type(new_auth) >= net_auth_type(old_auth))
		return TRUE;
	else
		return FALSE;
}


/*
 * Turns binary data of given lenght into a newly-allocated HEX string.
 *
 *
 */
PRIVATE char *
bin2hex(unsigned char *data, int len)
{
    char *buf = (char *)XP_ALLOC(2 * len + 1);
    char *p = buf;

	if(!buf)
		return NULL;

    while (len-- > 0) {
		sprintf(p, "%02x", *data);
		p += 2;
		data++;
    }
    *p = '\0';
    return buf;
}


/*
 * Parse {WWW,Proxy}-Authenticate header parameters into a net_AuthStruct
 * structure.
 *
 */
#define SKIP_WS(p) while((*(p)) && XP_IS_SPACE(*(p))) p++

PRIVATE XP_Bool
next_params(char **pp, char **name, char **value)
{
    char *q, *p = *pp;

    SKIP_WS(p);
    if (!p || !(*p) || !(q = strchr(p, '=')))
		return FALSE;
    *name = p;
    *q++ = '\0';
    if (*q == '"') {
		*value = q + 1;
		q = strchr(*value, '"');
		if (q)
		  *q++ = '\0';
    }
    else {
		*value = q;
		while (*q && !XP_IS_SPACE(*q)) q++;
		if (*q)
		  *q++ = '\0';
    }

    *pp = q;
    return TRUE;
}

PRIVATE net_AuthStruct *
net_parse_authenticate_line(char *auth, net_AuthStruct *ret)
{
    char *name, *value, *p = auth;

	if (!auth || !*auth)
		return NULL;

	if (!ret)
		ret = XP_NEW_ZAP(net_AuthStruct);

	if(!ret)
		return NULL;

    SKIP_WS(p);
	ret->auth_type = net_auth_type(p);
    while (*p && !XP_IS_SPACE(*p)) p++;

    while (next_params(&p, &name, &value)) {
		if (!strcasecomp(name, "realm"))
		  {
			  StrAllocCopy(ret->realm, value);
		  }
#ifdef SIMPLE_MD5
		else if (!strcasecomp(name, "domain"))
		  {
			  StrAllocCopy(ret->domain, value);
		  }
		else if (!strcasecomp(name, "nonce"))
		  {
			  StrAllocCopy(ret->nonce, value);
		  }
		else if (!strcasecomp(name, "opaque"))
		  {
			  StrAllocCopy(ret->opaque, value);
		  }
		else if (!strcasecomp(name, "oldnonce"))
		  {
			  ret->oldNonce = (!strcasecomp(value, "TRUE")) ? TRUE : FALSE;
		  }
#endif /* SIMPLE_MD5 */
		/* Some FORTEZZA checks */
		else if (!strcasecomp(name, "challenge"))
		  {
			  StrAllocCopy(ret->challenge, value);
		  }
		else if (!strcasecomp(name, "oldchallenge"))
		  {
			  ret->oldChallenge = (!strcasecomp(value, "TRUE")) ? TRUE : FALSE;
		  }
		/* End FORTEZZA checks  */
    }

#ifdef SIMPLE_MD5
	if (!ret->oldNonce)
		ret->oldNonce_retries = 0;
#endif /* SIMPLE_MD5 */
	/*Another FORTEZZA addition*/
	if (!ret->oldChallenge)
		ret->oldChallenge_retries = 0;
	/*End FPRTEZZA  addition   */
    return ret;
}


#ifdef SIMPLE_MD5
/* ---------- New stuff: SimpleMD5 Authentication for proxies -------------- */

PRIVATE void do_md5(unsigned char *stuff, unsigned char digest[16])
{
	MD5Context *cx = MD5_NewContext();
	unsigned int len;

	if (!cx)
		return;

	MD5_Begin(cx);
	MD5_Update(cx, stuff, strlen((char*)stuff));
	MD5_End(cx, digest, &len, 16);	/* len had better be 16 when returned! */

	MD5_DestroyContext(cx, (DSBool)TRUE);
}


/*
 * Generate a response for a SimpleMD5 challenge.
 *
 *	HEX( MD5("challenge password method url"))
 *
 */
char *net_generate_md5_challenge_response(char *challenge,
										  char *password,
										  int   method,
										  char *url)
{
    unsigned char digest[16];
    unsigned char *cookie =
	  (unsigned char *)XP_ALLOC(strlen(challenge) + strlen(password) +
								strlen(url) + 10);

	if(!cookie)
		return NULL;

    sprintf((char *)cookie, "%s %s %s %s", challenge, password,
			(method==URL_POST_METHOD ? "POST" :
			 method==URL_HEAD_METHOD ? "HEAD" : "GET"),
			url);
    do_md5(cookie, digest);
    return bin2hex(digest, 16);
}


#define SIMPLEMD5_AUTHORIZATION_FMT "SimpleMD5\
 username=\"%s\",\
 realm=\"%s\",\
 nonce=\"%s\",\
 response=\"%s\",\
 opaque=\"%s\""

#endif /* SIMPLE_MD5 */

PRIVATE
char *net_generate_auth_string(URL_Struct *url_s,
							   net_AuthStruct *auth_s)
{
	if (!auth_s)
		return NULL;

	switch (auth_s->auth_type) {

	  case AUTH_INVALID:
		break;

	  case AUTH_BASIC:
		if (!auth_s->auth_string) {
			int len;
			char *u_pass_string = NULL;

			StrAllocCopy(u_pass_string, auth_s->username);
			StrAllocCat (u_pass_string, ":");
			StrAllocCat (u_pass_string, auth_s->password);

			len = XP_STRLEN(u_pass_string);
			if (!(auth_s->auth_string = (char*) XP_ALLOC((((len+1)*4)/3)+20)))
			  {
				  return NULL;
			  }

			XP_STRCPY(auth_s->auth_string, "Basic ");
			NET_UUEncode((unsigned char *)u_pass_string,
						 (unsigned char *)&auth_s->auth_string[6],
						 len);

			FREE(u_pass_string);
		}
		break;

#ifdef SIMPLE_MD5
	  case AUTH_SIMPLEMD5:
	    if (auth_s->username && auth_s->password &&
			auth_s->nonce    && auth_s->opaque   &&
			url_s            && url_s->address)
		  {
			  char *resp;

			  FREEIF(auth_s->auth_string);
			  auth_s->auth_string = NULL;

			  if ((resp = net_generate_md5_challenge_response(auth_s->nonce,
															  auth_s->password,
															  url_s->method,
															  url_s->address)))
				{
					if ((auth_s->auth_string =
						 (char *)XP_ALLOC(XP_STRLEN(auth_s->username) +
										  XP_STRLEN(auth_s->realm)    +
										  XP_STRLEN(auth_s->nonce)    +
										  XP_STRLEN(resp)             +
										  XP_STRLEN(auth_s->opaque)   +
										  100)))
					  {
						  sprintf(auth_s->auth_string,
								  SIMPLEMD5_AUTHORIZATION_FMT,
								  auth_s->username,
								  auth_s->realm,
								  auth_s->nonce,
								  resp,
								  auth_s->opaque);
					  }
					FREE(resp);
				}
		  }
		break;
#endif /* SIMPLE_MD5 */
		/* Handle the FORTEZZA case        */
	        case AUTH_FORTEZZA:
		  if (auth_s->signature && auth_s->challenge && 
		      auth_s->certChain && auth_s->clientRan) {
		          int len;

			  FREEIF(auth_s->auth_string);
			  auth_s->auth_string = NULL;

			  len = XP_STRLEN(auth_s->signature) + XP_STRLEN(auth_s->challenge)
			        + XP_STRLEN(auth_s->certChain) + XP_STRLEN(auth_s->clientRan) + 100;
			  auth_s->auth_string = (char *)XP_ALLOC(len);
			  if (auth_s->auth_string) {
				sprintf(auth_s->auth_string,"signature=\"%s\" challenge=\"%s\" "
					"clientRan=\"%s\" certChain=\"%s\"",auth_s->signature,
					auth_s->challenge, auth_s->clientRan, auth_s->certChain);
			  }
		  }
		  break;
		/* Done Handling the FORTEZZA case */
    }

	return auth_s->auth_string;
}


/* --------------- New stuff: client-proxy authentication --------------- */

PRIVATE net_AuthStruct *
net_CheckForProxyAuth(char * proxy_addr)
{
	XP_List * lp = net_proxy_auth_list;
	net_AuthStruct * s;

	while ((s = (net_AuthStruct *)XP_ListNextObject(lp)) != NULL)
      {
		  if (!strcasecomp(s->proxy_addr, proxy_addr))
			  return s;
	  }

    return NULL;
}


/*
 * returns a proxy authorization string if one is required, otherwise
 * returns NULL
 */
PUBLIC char *
NET_BuildProxyAuthString(MWContext * context,
						 URL_Struct * url_s,
						 char * proxy_addr)
{
	net_AuthStruct * auth_s = net_CheckForProxyAuth(proxy_addr);

	return auth_s ? net_generate_auth_string(url_s, auth_s) : NULL;
}


/*
 * Returns FALSE if the user wishes to cancel proxy authorization
 * and TRUE if the user wants to continue with a new authorization
 * string.
 */
#define INVALID_AUTH_HEADER XP_GetString( XP_PROXY_REQUIRES_UNSUPPORTED_AUTH_SCHEME )

#define LOOPING_OLD_NONCES XP_GetString( XP_LOOPING_OLD_NONCES )

PUBLIC XP_Bool
NET_AskForProxyAuth(MWContext * context,
					char *   proxy_addr,
					char *   pauth_params,
					XP_Bool  already_sent_auth)
{
	net_AuthStruct * prev;
	XP_Bool new_entry = FALSE;
	char * username = NULL;
	char * password = NULL;
	char * buf;
	int32  len=0;

	TRACEMSG(("Entering NET_AskForProxyAuth"));

	if (!proxy_addr || !*proxy_addr || !pauth_params || !*pauth_params)
		return FALSE;

	prev = net_CheckForProxyAuth(proxy_addr);
    if (prev) {
		new_entry = FALSE;
		net_parse_authenticate_line(pauth_params, prev);
	}
	else {
		new_entry = TRUE;
		if (!(prev = net_parse_authenticate_line(pauth_params, NULL)))
		  {
			  FE_Alert(context, INVALID_AUTH_HEADER);
			  return FALSE;
		  }
		StrAllocCopy(prev->proxy_addr, proxy_addr);
	}

	if (!prev->realm || !*prev->realm)
		StrAllocCopy(prev->realm, XP_GetString( XP_UNIDENTIFIED_PROXY_SERVER ) );

    if (!new_entry) {
		if (!already_sent_auth)
		  {
			  /* somehow the mapping changed since the time we sent
			   * the authorization.
			   * This happens sometimes because of the parrallel
			   * nature of the requests.
			   * In this case we want to just retry the connection
			   * since it will probably succeed now.
			   */
			  return TRUE;
		  }
#ifdef SIMPLE_MD5
		else if (prev->oldNonce && prev->oldNonce_retries++ < 3)
		  {
			  /*
			   * We already sent the authorization string and the
			   * nonce was expired -- auto-retry.
			   */
			  if (!FE_Confirm(context, LOOPING_OLD_NONCES))
				  return FALSE;
		  }
#endif /* SIMPLE_MD5 */
		/* Do the good old FORTEZZA  stuff */
		else if (prev->oldChallenge && (prev->oldChallenge_retries++ > 3)) 
		  {
			  /*
			   * We already sent the authorization string and the
			   * nonce was expired -- auto-retry.
			   */
			  if (!FE_Confirm(context, XP_GetString(XP_CONFIRM_PROXYAUTHOR_FAIL)))
				  return FALSE;
		  }
		else if (prev->auth_type != AUTH_FORTEZZA)
		  {
			  /*
			   * We already sent the authorization string and it failed.
			   */
			  if (!FE_Confirm(context, XP_GetString(XP_CONFIRM_PROXYAUTHOR_FAIL)))
				  return FALSE;
		  }
	}

    
    if (prev->auth_type == AUTH_FORTEZZA) {
		SECStatus rv;
		rv = SECNAV_ComputeFortezzaProxyChallengeResponse(context,
														  prev->challenge,
														  &prev->signature,
														  &prev->clientRan,
														  &prev->certChain);
		if ( rv != SECSuccess ) {
			return(FALSE);
		}
    } else
    {
	username = prev->username;
	password = prev->password;

	len = XP_STRLEN(prev->realm) + XP_STRLEN(proxy_addr) + 50;
	buf = (char*)XP_ALLOC(len*sizeof(char));
	
	if(buf)
	  {
		PR_snprintf(buf, len*sizeof(char), XP_GetString( XP_PROXY_AUTH_REQUIRED_FOR ), prev->realm, proxy_addr);

		NET_Progress(context, XP_GetString( XP_CONNECT_PLEASE_ENTER_PASSWORD_FOR_PROXY ) );
		len = FE_PromptUsernameAndPassword(context, buf, 
									       &username, &password);
		FREE(buf);
	  }
	else
	  {
		len = 0;
	  }

	if (!len)
	  {
		  TRACEMSG(("User canceled login!!!"));
		  return FALSE;
	  }
	else if (!username || !password)
	  {
		  return FALSE;
	  }

	FREEIF(prev->auth_string);
	prev->auth_string = NULL;		/* Generate a new one */
	FREEIF(prev->username);
	prev->username = username;
	FREEIF(prev->password);
	prev->password = password;
    }

	if (new_entry)
	  {
		  if (!net_proxy_auth_list)
			{
				net_proxy_auth_list = XP_ListNew();
				if (!net_proxy_auth_list)
				  {
					  return TRUE;
				  }
			}
		  XP_ListAddObjectToEnd(net_proxy_auth_list, prev);
	  }

	return TRUE;
}

#define BUFLEN 2048
/* create an HTML stream and push a bunch of HTML about cookies. Such as:
 * GENERAL INFO
 * The number of cookies you have in mem (expired cookies don't show up).
 * The maximum number allowed.
 * The maximum allowed per server.
 * The maximum allowable size of a cookie (in bytes).
 *
 * PER COOKIE INFO
 * Cookie name & Value.
 * The host it came from.
 * Whether or not there's a domain.
 * The path the cookie will be sent to.
 * Whether or not the cookie is sent secure.
 * When the cookie expires.
 * 
 */
MODULE_PRIVATE void 
NET_DisplayCookieInfoAsHTML(ActiveEntry * cur_entry) {
	char *buffer=(char*)XP_ALLOC(BUFLEN), *expireDate=NULL;
   	NET_StreamClass *stream;
	int i, g, numOfCookies;
	XP_List *list=net_cookie_list;
	net_CookieStruct *cookie;

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

	/* The the current entry's content type */
	StrAllocCopy(cur_entry->URL_s->content_type, TEXT_HTML);

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

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


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

	/* Get rid of any expired cookies now so user doesn't
	 * think/see that we're keeping cookies in mem.
	 */
	net_remove_expired_cookies();
	numOfCookies=XP_ListCount(net_cookie_list);

	/* Write out the initial statistics. */
	g = PR_snprintf(buffer, BUFLEN,
"<TITLE>%s</TITLE>\n"
"<h2>%s</h2>\n"
"<TABLE>\n"
"<TR>\n",
		XP_GetString(MK_ACCESS_YOUR_COOKIES),
		XP_GetString(MK_ACCESS_YOUR_COOKIES));

	g += PR_snprintf(buffer+g, BUFLEN-g,
"<TD ALIGN=RIGHT><b>%s</TD>\n"
"<TD>%ld</TD>\n"
"</TR>\n"
"<TR>\n",
		XP_GetString(MK_ACCESS_MAXIMUM_COOKS),
		MAX_NUMBER_OF_COOKIES);

	g += PR_snprintf(buffer+g, BUFLEN-g,
"<TD ALIGN=RIGHT><b>%s</TD>\n"
"<TD>%ld</TD>\n"
"</TR>\n"
"<TR>\n",
		XP_GetString(MK_ACCESS_COOK_COUNT),
		numOfCookies);

	g += PR_snprintf(buffer+g, BUFLEN-g,
"<TD ALIGN=RIGHT><b>%s</TD>\n"
"<TD>%ld</TD>\n"
"</TR>\n"
"<TR>\n",
		XP_GetString(MK_ACCESS_MAXIMUM_COOKS_PER_SERV),
		MAX_COOKIES_PER_SERVER);

	g += PR_snprintf(buffer+g, BUFLEN-g,
"<TD ALIGN=RIGHT><b>%s</TD>\n"
"<TD>%ld</TD>\n"
"</TR>\n"
"</TABLE>\n"
"<HR>",
		XP_GetString(MK_ACCESS_MAXIMUM_COOK_SIZE),
		MAX_BYTES_PER_COOKIE);


	PUT_PART(buffer);

	if(!numOfCookies) {
		XP_STRCPY(buffer, XP_GetString(MK_ACCESS_NO_COOKIES));
		PUT_PART(buffer);
		goto END;
	  }

/* define some macros to help us output HTML */
#define HEADING(arg1)					\
	XP_STRCPY(buffer, "<tt>");			\
	for(i=XP_STRLEN(arg1); i < 16; i++)	\
		XP_STRCAT(buffer, "&nbsp;");	\
	XP_STRCAT(buffer, arg1);			\
	XP_STRCAT(buffer, " </tt>");		\
	PUT_PART(buffer);

#define BRCRLF					\
	XP_STRCPY(buffer, "<BR>\n");		\
	PUT_PART(buffer);
/* End html macros */

	/* Write out each cookie */
	while ( (cookie=(net_CookieStruct *) XP_ListNextObject(list)) ) {

		HEADING(XP_GetString(MK_ACCESS_NAME));
		XP_STRCPY(buffer, cookie->name);
		PUT_PART(buffer);
		BRCRLF;

		HEADING(XP_GetString(MK_ACCESS_VALUE));
		XP_STRCPY(buffer, cookie->cookie);
		PUT_PART(buffer);
		BRCRLF;

		HEADING(XP_GetString(MK_ACCESS_HOST));
		XP_STRCPY(buffer, cookie->host);
		PUT_PART(buffer);
		BRCRLF;

		HEADING(XP_GetString(MK_ACCESS_SEND_TO_HOST));
		if(cookie->is_domain)
			XP_STRCPY(buffer, XP_GetString(MK_ACCESS_IS_DOMAIN));
		else
			XP_STRCPY(buffer, XP_GetString(MK_ACCESS_IS_NOT_DOMAIN));
		PUT_PART(buffer);
		BRCRLF;

		HEADING(XP_GetString(MK_ACCESS_SEND_TO_PATH));
		XP_STRCPY(buffer, cookie->path);
		PUT_PART(buffer);
		XP_STRCPY(buffer, XP_GetString(MK_ACCESS_AND_BELOW));
		PUT_PART(buffer);
		BRCRLF;

		HEADING(XP_GetString(MK_ACCESS_SECURE));
		if(cookie->secure)
			XP_STRCPY(buffer, "Yes");
		else
			XP_STRCPY(buffer, "No");
		PUT_PART(buffer);
		BRCRLF;

		HEADING(XP_GetString(MK_ACCESS_EXPIRES));
		if(cookie->expires) {
			expireDate=ctime(&(cookie->expires));
			if(expireDate)
				XP_STRCPY(buffer, expireDate);
			else
				XP_STRCPY(buffer, "NULL");
		} else {
			XP_STRCPY(buffer, XP_GetString(MK_ACCESS_END_OF_SESSION));
		}
		PUT_PART(buffer);
		XP_STRCPY(buffer, " GMT");
		PUT_PART(buffer);
		BRCRLF;

		XP_STRCPY(buffer, "\n<P>\n");
		PUT_PART(buffer);
	}
	/* End each cookie */

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

