/* -*- 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.
 */
/*
 * Glue code to make PICS work in the client.  This is just a thin
 * layer between the W3C code and our layout engine to digest PICS labels.
 * Coded by Lou Montulli
 */

#include "xp.h"
#include "cslutils.h"
#include "csll.h"
#include "csllst.h"
#include "pics.h"
#include "prefapi.h"
#include "xpgetstr.h"
#include "sechash.h"
#include "base64.h"

extern int XP_ALERT_PROMPT_JAVA_CAPIBILITIES_PASSWORD;
extern int XP_ALERT_PROMPT_JAVA_CAPIBILITIES_PASSWORD_FAILED_ONCE;

typedef struct {
	PICS_RatingsStruct * rs;
	XP_Bool rs_invalid;
} ClosureData;

PRIVATE StateRet_t 
target_callback(CSLabel_t *pCSLabel,
		CSParse_t * pCSParse,
 		CSLLTC_t target, XP_Bool closed,
 		void * pClosure)
{
	char * ratingname;
	char * ratingstr;
	ClosureData *cd = (ClosureData *)pClosure;
	
	/* closed signifies that the parsing is done for that label */
    if(!cd || !closed)
    	return StateRet_OK;

    if(target == CSLLTC_SINGLE)
    {
        LabelOptions_t * lo = CSLabel_getLabelOptions(pCSLabel);

		if(lo)
		{
			if(lo->generic.state)
			{
				cd->rs->generic = TRUE;
			}

			if(lo->fur.value && !cd->rs->fur)
			{
				StrAllocCopy(cd->rs->fur, lo->fur.value);
			}
		}
    }
	else if(target ==  CSLLTC_RATING)
	{
    	PICS_RatingsStruct *rating_struct = cd->rs;
        LabelOptions_t * lo = CSLabel_getLabelOptions(pCSLabel);

        ratingstr = CSLabel_getRatingStr(pCSLabel);
		ratingname =  CSLabel_getRatingName(pCSLabel);

		if(ratingname)
		{
			LabelRating_t * label_rating;
			ServiceInfo_t * service_info;

			service_info = CSLabel_getServiceInfo(pCSLabel);

			if(service_info && !rating_struct->service)
			{
			   rating_struct->service = XP_STRDUP(service_info->rating_service.value); 
			}

			label_rating = CSLabel_getLabelRating(pCSLabel);

			if(label_rating)
			{
                double value;
				PICS_RatingValue *rating_value = XP_NEW_ZAP(PICS_RatingValue);

                value = label_rating->value.value;
					
				if(rating_value)
				{
					rating_value->value = label_rating->value.value;
					rating_value->name = XP_STRDUP(label_rating->identifier.value);

					if(rating_value->name)
					{
						/* insert it into the list */
						XP_ListAddObject(rating_struct->ratings, rating_value);
					}
					else
					{
						/* error, cleanup */
						XP_FREE(rating_value);
					}
				}
            }
		}

	}
	return StateRet_OK;
}

PRIVATE StateRet_t 
parse_error_handler(CSLabel_t * pCSLabel, CSParse_t * pCSParse,
                             const char * token, char demark,
                             StateRet_t errorCode)
{
	return errorCode;
}

/* return NULL or ratings struct */
PUBLIC PICS_RatingsStruct * 
PICS_ParsePICSLable(char * label)
{
	CSParse_t *CSParse_handle;
    ClosureData *cd;
	PICS_RatingsStruct *rs;
    CSDoMore_t status;

	if(!label)
		return NULL;

	cd = XP_NEW_ZAP(ClosureData);

	if(!cd)
		return NULL;

	rs = XP_NEW_ZAP(PICS_RatingsStruct);

	if(!rs)
	{
		XP_FREE(cd);
		return NULL;
	}

    rs->ratings = XP_ListNew();

	cd->rs = rs;

	/* parse pics label using w3c api */

	CSParse_handle = CSParse_newLabel(&target_callback, &parse_error_handler);

	if(!CSParse_handle)
		return NULL;

	do {

		status = CSParse_parseChunk(CSParse_handle, label, XP_STRLEN(label), cd);

	} while(status == CSDoMore_more);

	if(cd->rs_invalid)
	{
		PICS_FreeRatingsStruct(rs);
		rs = NULL;
	}

	XP_FREE(cd);

    CSParse_deleteLabel(CSParse_handle);

	return(rs);
}

PUBLIC void
PICS_FreeRatingsStruct(PICS_RatingsStruct *rs)
{
	if(rs)
	{
        PICS_RatingValue *rv;
        
        while((rv = XP_ListRemoveTopObject(rs->ratings)) != NULL)
        {
            XP_FREE(rv->name);
            XP_FREE(rv);
        }

		XP_FREE(rs);
	}
}

#define PICS_DOMAIN  			"browser.PICS."
#define PICS_ENABLED_PREF 		PICS_DOMAIN"ratings_enabled"
#define PICS_MUST_BE_RATED_PREF PICS_DOMAIN"pages_must_be_rated"
#define PICS_DISABLED_FOR_SESSION PICS_DOMAIN"disable_for_this_session"
#define PICS_REENABLE_FOR_SESSION PICS_DOMAIN"reenable_for_this_session"

#define JAVA_SECURITY_PASSWORD "signed.applets.capabilitiesDB.password"

Bool pics_ratings_enabled = FALSE;
Bool pics_pages_must_be_rated_pref = FALSE;
Bool pics_disabled_for_this_session = FALSE;
int pics_violence_pref = 0;
int pics_sexual_pref = 0;
int pics_language_pref = 0;
int pics_nudity_pref = 0;

/* if TRUE the user can allow additional java and JS
 * capibilities.  UniversalFileWrite, etc.
 */
Bool pics_java_capabilities_enabled = TRUE;

int PR_CALLBACK
pics_pref_change(const char *pref_name, void *closure)
{
    XP_Bool bool_rv;

    if(!PREF_GetBoolPref(PICS_ENABLED_PREF, &bool_rv))
        pics_ratings_enabled = bool_rv;
    if(!PREF_GetBoolPref(PICS_MUST_BE_RATED_PREF, &bool_rv))
        pics_pages_must_be_rated_pref = bool_rv;
    if(!PREF_GetBoolPref(PICS_DISABLED_FOR_SESSION, &bool_rv))
	{
		if(bool_rv)
		{
        	pics_disabled_for_this_session = TRUE;
    		PREF_SetBoolPref(PICS_DISABLED_FOR_SESSION, FALSE);
		}
	}
    if(!PREF_GetBoolPref(PICS_REENABLE_FOR_SESSION, &bool_rv))
	{
		if(bool_rv)
		{
        	pics_disabled_for_this_session = FALSE;
    		PREF_SetBoolPref(PICS_REENABLE_FOR_SESSION, FALSE);
		}
	}

    return 0;
}

PRIVATE char *
pics_hash_password(char *pw)
{
        SECStatus status;
        unsigned char result[SHA1_LENGTH];

        status = SHA1_HashBuf(result, (unsigned char *)pw, XP_STRLEN(pw));

	if (status != SECSuccess)
		return NULL;

	return(BTOA_DataToAscii(result, SHA1_LENGTH));
}

PUBLIC void
PICS_Init(MWContext *context)
{
    static XP_Bool first_time=TRUE;

    if(!first_time)
    {
        return;
    }
    else
    {
	    char *password=NULL;

        first_time = FALSE;

	    /* get the prefs */
	    pics_pref_change(PICS_DOMAIN, NULL);

        PREF_RegisterCallback(PICS_DOMAIN, pics_pref_change, NULL);

	    /* check for security pref that password disables the enableing of
	     * java permissions
	     */
        if(PREF_CopyCharPref(JAVA_SECURITY_PASSWORD, &password))
		    password = NULL;

	    if(password && *password)
	    {
		    /* get prompt string from registry
		     */
		    char *prompt_string = XP_GetString(XP_ALERT_PROMPT_JAVA_CAPIBILITIES_PASSWORD);
		    char *user_password;
		    char *hashed_password;

prompt_again:

		    /* prompt the user for the password
		     */
		    user_password = FE_PromptPassword(context, prompt_string);

		    /* ### one-way hash password */
		    if(user_password)
		    {
				hashed_password = pics_hash_password(user_password);
		    }
            else
            {
                hashed_password = NULL;
            }

		    if(!hashed_password)
            {
                pics_java_capabilities_enabled = FALSE;
            }
            else if(!XP_STRCMP(hashed_password, password))
		    {
			    pics_java_capabilities_enabled = TRUE;
		    }
		    else
		    {
                XP_FREE(user_password);
                XP_FREE(hashed_password);
       		    prompt_string = XP_GetString(XP_ALERT_PROMPT_JAVA_CAPIBILITIES_PASSWORD_FAILED_ONCE);
                goto prompt_again;
		    }

		    XP_FREEIF(user_password);
		    XP_FREEIF(hashed_password);
		    
	    }
	    
	    XP_FREEIF(password);
    }
}

PUBLIC XP_Bool
PICS_CanUserEnableAdditionalJavaCapabilities(void)
{
	return(pics_java_capabilities_enabled);
}

PUBLIC XP_Bool
PICS_IsPICSEnabledByUser(void)
{
	/* short circuit */
	if(pics_disabled_for_this_session)
		return FALSE;

    return(pics_ratings_enabled);
}

PUBLIC XP_Bool
PICS_AreRatingsRequired(void)
{
	return pics_pages_must_be_rated_pref;
}

PRIVATE char *
illegal_to_underscore(char *string)
{
    char* ptr = string;

    if(!string)
       return NULL;

    if(!XP_IS_ALPHA(*ptr))
           *ptr = '_';

    for(ptr++; *ptr; ptr++)
        if(!XP_IS_ALPHA(*ptr) && !XP_IS_DIGIT(*ptr))
           *ptr = '_';

    return string;
}

PRIVATE char *
lowercase_string(char *string)
{
    char *ptr = string;

    if(!string)
        return NULL;

    for(; *ptr; ptr++)
        *ptr = XP_TO_LOWER(*ptr);

    return string;
}

#define PICS_URL_PREFIX "about:pics"

/* returns a URL string from a RatingsStruct
 * that includes the service URL and rating info
 */
PUBLIC char *
PICS_RStoURL(PICS_RatingsStruct *rs, char *cur_page_url)
{
    char *rv; 
    char *escaped_cur_page=NULL;

    if(cur_page_url)
    {
        escaped_cur_page = NET_Escape(cur_page_url, URL_PATH);    
        if(!escaped_cur_page)
            return NULL;
    }

    rv = PR_smprintf("%s?Destination=%s", 
                     PICS_URL_PREFIX, 
                     escaped_cur_page ? escaped_cur_page : "none");

    XP_FREE(escaped_cur_page);

    if(!rs || !rs->service)
    {
        StrAllocCat(rv, "&NO_RATING");
        return(rv);
    }
    else
    {
		XP_List *list_ptr = rs->ratings;
		PICS_RatingValue *rating_value;
    	char *escaped_service = NET_Escape(rs->service, URL_PATH);    

    	if(!escaped_service)
        	return NULL;

        StrAllocCat(rv, "&Service=");
        StrAllocCat(rv, escaped_service);

        XP_FREE(escaped_service);

        while((rating_value = XP_ListNextObject(list_ptr)) != NULL)
        {

            char *add;
            char *escaped_name = NET_Escape(
                                     illegal_to_underscore(rating_value->name),
                                     URL_PATH);

            if(!escaped_name)
            {
                XP_FREE(rv); 
                return NULL;
            }
            
            add = PR_smprintf("&%s=%f", escaped_name, rating_value->value);

            XP_FREE(escaped_name);

            StrAllocCat(rv, add);

            XP_FREE(add);
        }

        return rv;
    }

    XP_ASSERT(0); /* should never get here */
    return NULL;
}

XP_List *pics_tree_ratings=NULL;

PRIVATE void
pics_add_rs_to_tree_ratings(PICS_RatingsStruct *rs)
{
	char *path;

	if(!pics_tree_ratings)
	{
		pics_tree_ratings = XP_ListNew();
		if(!pics_tree_ratings)
			return;
	}

	if(!rs->fur || !rs->generic)
		return;   /* doesn't belong here */

	/* make sure it's not in the list already */
	if(PICS_CheckForValidTreeRating(rs->fur))
		return;

	/* make sure the fur address smells like a URL and has
	 * a real host name (at least two dots) 
	 *
	 * reject "http://" or "http://www" 
	 */
	if(!NET_URL_Type(rs->fur))
		return;

	path = NET_ParseURL(rs->fur, GET_PATH_PART);

	/* if it has a path it's ok */
	if(!path || !*path)
	{
		/* if it doesn't have a path it needs at least two dots */
		char *ptr;
		char *hostname = NET_ParseURL(rs->fur, GET_HOST_PART);

		if(!hostname)
			return;

		if(!(ptr = XP_STRCHR(hostname, '.'))
			|| !XP_STRCHR(ptr+1, '.'))
		{
			XP_FREE(hostname);
			XP_FREEIF(path);
			return;
		} 

		XP_FREE(hostname);
	}

	XP_FREE(path);

	XP_ListAddObject(pics_tree_ratings, rs->fur);

	return;
}

PUBLIC XP_Bool
PICS_CheckForValidTreeRating(char *url_address)
{
	XP_List *list_ptr;
	char *valid_tree;

	if(!pics_tree_ratings)
		return FALSE;

	list_ptr = pics_tree_ratings;

	while((valid_tree = XP_ListNextObject(list_ptr)))
	{
		if(!XP_STRNCASECMP(url_address, valid_tree, XP_STRLEN(valid_tree)))
			return TRUE;
	}

	return FALSE;
}

/* returns TRUE if page should be censored
 * FALSE if page is allowed to be shown
 */
PUBLIC PICS_PassFailReturnVal
PICS_CompareToUserSettings(PICS_RatingsStruct *rs, char *cur_page_url)
{
	int32 int_pref;
	XP_Bool bool_pref;
	char * pref_prefix;
	char * pref_string=NULL;
	char * escaped_service;
	PICS_PassFailReturnVal rv = PICS_RATINGS_PASSED;
    XP_List *list_ptr;
    PICS_RatingValue *rating_value;

	if(!rs || !rs->service)
	{
		return PICS_NO_RATINGS;
	}

#define PICS_SERVICE_DOMAIN   PICS_DOMAIN"service."
#define PICS_SERVICE_ENABLED  "service_enabled"

	/* cycle through list of ratings and compare to the users prefs */
	list_ptr = rs->ratings;
	pref_prefix = XP_STRDUP(PICS_SERVICE_DOMAIN);

    /* need to deal with bad characters */
	escaped_service = XP_STRDUP(rs->service);
    escaped_service = illegal_to_underscore(escaped_service);
    escaped_service = lowercase_string(escaped_service);

	if(!escaped_service)
		return PICS_RATINGS_FAILED;

	StrAllocCat(pref_prefix, escaped_service);

	XP_FREE(escaped_service);

	if(!pref_prefix)
		return PICS_RATINGS_FAILED;

	/* verify that this type of rating system is enabled */
	pref_string = PR_smprintf("%s.%s", pref_prefix, PICS_SERVICE_ENABLED);
	
    if(!pref_string)
        goto cleanup;

	if(!PREF_GetBoolPref(pref_string, &bool_pref))
	{
		if(!bool_pref)
		{
			/* this is an unenabled ratings service */
			rv = PICS_NO_RATINGS;
            XP_FREE(pref_string);
			goto cleanup;
		}
	}
	else
	{
		/* this is an unsupported ratings service */
		rv = PICS_NO_RATINGS;
        XP_FREE(pref_string);
		goto cleanup;
	}

    XP_FREE(pref_string);

	while((rating_value = XP_ListNextObject(list_ptr)) != NULL)
	{
		/* compose pref lookup string */
		pref_string = PR_smprintf("%s.%s", 
                                pref_prefix, 
                                illegal_to_underscore(rating_value->name));

		if(!pref_string)
			goto cleanup;

		/* find the value in the prefs if it exists
		 * if it does compare it to the value given and if
		 * less than, censer the page.
		 */
		if(!PREF_GetIntPref(pref_string, &int_pref))
		{
			if(rating_value->value > int_pref)
			{
				rv = PICS_RATINGS_FAILED;
                XP_FREE(pref_string);
				goto cleanup;
			}
		}

		XP_FREE(pref_string);
	}

cleanup:

	XP_FREE(pref_prefix);

	/* make sure this rating applies to this page */
	if(rs->fur)
	{
		if(XP_STRNCASECMP(cur_page_url, rs->fur, XP_STRLEN(rs->fur)))
			rv = PICS_NO_RATINGS;
	}

	if(rv == PICS_RATINGS_PASSED && rs->generic)
	{
		/* rating should apply to a whole tree, add to list */
		pics_add_rs_to_tree_ratings(rs);		
	}

	return rv;
}

