/* -*- Mode: C++; tab-width: 8; 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.
 */


#include "xp.h"

/*
 * SCRIPT tag support.
 */

#include "libmocha.h"
#include "lo_ele.h"
#include "net.h"
#include "pa_parse.h"
#include "pa_tags.h"
#include "layout.h"
#include "libevent.h"
#include "css.h"
#include "laystyle.h"
#include "mcom_db.h"
#include "laylayer.h"
#include "prefapi.h"

static char lo_jsAllowFileSrcFromNonFile[]
    = "javascript.allow.file_src_from_non_file";

/*
 * XXX Should dynamic layer resize-reload really rerun scripts, potentially
 * XXX destroying and reinitializing JS state?  An onResize event handler
 * XXX would be consistent with how windows and static layers resize-reload.
 */
#define SCRIPT_EXEC_OK(top_state, state, type, script_type)                   \
        (top_state->resize_reload == FALSE ||                                 \
         type != script_type ||                                               \
         (lo_InsideLayer(state) && (lo_IsAnyCurrentAncestorDynamic(state) ||  \
                                    lo_IsAnyCurrentAncestorSourced(state))))

static void
lo_ParseScriptLanguage(MWContext * context, PA_Tag * tag, int8 * type,
		       JSVersion * version)
{
    PA_Block buff;
    char * str;

    buff = lo_FetchParamValue(context, tag, PARAM_LANGUAGE);
    if (buff != NULL) {
        PA_LOCK(str, char *, buff);
        if ((XP_STRCASECMP(str, js_language_name) == 0) ||
            (XP_STRCASECMP(str, "LiveScript") == 0) ||
            (XP_STRCASECMP(str, "Mocha") == 0)) {
            *type = SCRIPT_TYPE_MOCHA;
            *version = JSVERSION_DEFAULT;
        } 
        else if (XP_STRCASECMP(str, "JavaScript1.1") == 0) {
            *type = SCRIPT_TYPE_MOCHA;
            *version = JSVERSION_1_1;
        } 
        else if (XP_STRCASECMP(str, "JavaScript1.2") == 0) {
            *type = SCRIPT_TYPE_MOCHA;
            *version = JSVERSION_1_2;
        } 
        else if (XP_STRCASECMP(str, "JavaScript1.3") == 0) {
            *type = SCRIPT_TYPE_MOCHA;
            *version = JSVERSION_1_3;
        } 

        else {
            *type = SCRIPT_TYPE_UNKNOWN;
        }
        PA_FREE(buff);
    }
}

void
lo_BlockScriptTag(MWContext *context, lo_DocState *state, PA_Tag *tag)
{
    lo_TopState *top_state;
    pa_DocData *doc_data;

    if (!LM_CanDoJS(context))
	return;

    /*
     * Find the parser stream from its private data, doc_data, pointed at by
     * a top_state member for just this situation.
     */
    top_state = state->top_state;
    doc_data = top_state->doc_data;
    XP_ASSERT(doc_data != NULL && doc_data->url_struct != NULL);
    if (doc_data == NULL || doc_data->url_struct == NULL)   /* XXX paranoia */
        return;

    /*
     * The parser must not parse ahead after a script or style end-tag, or
     * it could get into a brute_tag state (XMP, PRE, etc.) and enter a mode
     * that would mess up document.writes coming from the script or style.
     * So we put it into overflow mode here, and decrement overflow when we
     * unblock.
     *
     * We test tag first to handle the call from lo_ProcessScriptTag, which
     * wants us to create an input state if needed, but not to count comment
     * bytes via lo_data.  Sad to say, this means lo_ProcessScriptTag must
     * also increment doc_data->overflow, but only if tag->is_end == 1 (a.k.a.
     * PR_TRUE), meaning the script tag was not blocked by an earlier blocking
     * tag that caused it to go through here and set is_end to 2.  XXX we can
     * do this only because all layout and libparse tests of is_end are of the
     * form "tag->is_end == FALSE".
     */
    if (tag) {
        /* Tag non-null means we're called from lo_BlockTag in layout.c. */
        if (tag->is_end == FALSE) {
			int8 script_type;
			JSVersion ver;

            /* Keep track of whether we're in a script or style container... */
			script_type = SCRIPT_TYPE_UNKNOWN;
			lo_ParseScriptLanguage(context, tag, &script_type, &ver);
			if (tag->type == P_STYLE || tag->type == P_LINK || 
				script_type != SCRIPT_TYPE_UNKNOWN)
				top_state->in_blocked_script = TRUE;
        }
        else if (top_state->in_blocked_script) {
            /* ...so we can avoid overflowing the parser on superfluous end
             * tags, such as those emitted by libmime.
             */
            top_state->in_blocked_script = FALSE;

            if (SCRIPT_EXEC_OK(top_state, state, tag->type, P_SCRIPT)) {
                tag->is_end = (PRPackedBool)2;
				PA_PushOverflow(doc_data);
				doc_data->overflow_depth ++;
            }
        }
    }

    /*
     * Count the bytes of HTML comments and heuristicly "lost" newlines in
     * tag so lo_ProcessScriptTag can recover it there later, in preference
     * to the (then far-advanced) value of doc_data->comment_bytes.  Ensure
     * that this hack is not confused for NULL by adding 1 here and taking
     * it away later.  Big XXX for future cleanup, needless to say.
     */
    if (tag)
        tag->lo_data = (void *)(doc_data->comment_bytes + 1);
    
    if (top_state->script_tag_count++ == 0)
        ET_SetDecoderStream(context, doc_data->parser_stream,
                            doc_data->url_struct, JS_FALSE);
}


typedef struct {
    MWContext *context;
    lo_DocState *state;
    PA_Tag *tag;
    char *url, *archiveSrc, *id, *codebase;
    char *buffer;
    uint32 bufferSize;
    JSVersion version;
    JSBool inlineSigned;
} ScriptData;

void 
lo_DestroyScriptData(void *arg)
{
    ScriptData *data = arg;

    if (data == NULL)
	return;
    XP_FREEIF(data->url);
    XP_FREEIF(data->archiveSrc);
    XP_FREEIF(data->id);
    XP_FREEIF(data->codebase);
    XP_FREEIF(data->buffer);
    if (data->tag)
	PA_FreeTag(data->tag);
    XP_FREE(data);
}

static void
lo_script_src_exit_fn(URL_Struct *url_struct, int status, MWContext *context);

static void
lo_script_archive_exit_fn(URL_Struct *url_struct, int status, MWContext *context);

static Bool
lo_create_script_blockage(MWContext *context, lo_DocState *state, int type)
{
    lo_TopState *top_state;
    LO_Element *block_ele;

    top_state = state->top_state;
    if (!top_state) {
        XP_ASSERT(0);
        return FALSE;
    }
    
    block_ele = lo_NewElement(context, state, LO_SCRIPT, NULL, 0);
    if (block_ele == NULL) {
        top_state->out_of_memory = TRUE;
	return FALSE;
    }

    block_ele->type = type;
    block_ele->lo_any.ele_id = NEXT_ELEMENT;
    top_state->layout_blocking_element = block_ele;
    if (type == LO_SCRIPT)
	top_state->current_script = block_ele;

    return TRUE;
}

/*
 * used for ARCHIVE= and SRC= 
 * Create name of form "archive.jar/src.js" 
 */
static char *
lo_BuildJSArchiveURL( char *archive_name, char *filename )
{
    uint32 len = XP_STRLEN(archive_name) + XP_STRLEN(filename) + 2;
    char *path = XP_ALLOC(len);
    if (path)
    {
        XP_STRCPY(path, archive_name);
        XP_STRCAT(path, "/");
        XP_STRCAT(path, filename);
    }
    return path;
}

/*
 * Load a document from an external URL.  Assumes that layout is
 *   already blocked
 */
static void
lo_GetScriptFromURL(ScriptData *data, int script_type) 
{
    URL_Struct *url_struct;
    lo_TopState *top_state = data->state->top_state;
    PA_Tag *tag = data->tag;

    url_struct = NET_CreateURLStruct(data->url, top_state->force_reload);
    if (url_struct == NULL) {
        top_state->out_of_memory = TRUE;
	lo_DestroyScriptData(data);
	return;
    }

    url_struct->must_cache = TRUE;
    url_struct->preset_content_type = TRUE;
    if (script_type == SCRIPT_TYPE_CSS) {
        StrAllocCopy(url_struct->content_type, TEXT_CSS);
    } else {
	if (tag->type == P_STYLE || tag->type == P_LINK) {
	    StrAllocCopy(url_struct->content_type, TEXT_JSSS);
	} else {
	    StrAllocCopy(url_struct->content_type, js_content_type);
	}
    }

    XP_ASSERT(top_state->layout_blocking_element);

    if (!url_struct->content_type)
	goto out;

    if (!data->inlineSigned) {
        if (data->archiveSrc) {
            /* ARCHIVE= and SRC= */
            /*
             * Need to set nesting url. Create name of form
             * "archive.jar/src.js" 
             */
             
            char *path = lo_BuildJSArchiveURL(url_struct->address,
                                              data->archiveSrc);    
            if (!path)
                goto out;
            ET_SetNestingUrl(data->context, path);
            /* version taken care of in lo_script_archive_exit_fn */
            XP_FREE(path);
        } else {
            /* SRC= but no ARCHIVE= */
            ET_SetNestingUrl(data->context, url_struct->address);
            ET_SetVersion(data->context, data->version);
        }
    }

    url_struct->fe_data = data;

    if (data->archiveSrc != NULL || data->inlineSigned) {
        NET_GetURL(url_struct, 
                   FO_CACHE_ONLY,
                   data->context,
                   lo_script_archive_exit_fn);
    } else {
        NET_GetURL(url_struct, 
                   FO_CACHE_AND_PRESENT, 
                   data->context,
                   lo_script_src_exit_fn);
    }

    return;
  out:
    top_state->out_of_memory = TRUE;
    NET_FreeURLStruct(url_struct);
    lo_DestroyScriptData(data);
    return;
}

/*
 * A script has just completed.  If we are blocked on that script
 *   free the blockage
 */
static void
lo_unblock_script_tag(MWContext * context, Bool messWithParser)
{
    lo_TopState *top_state;
    lo_DocState *state;
    LO_Element * block_ele;
    LO_Element * current_script;
    PA_Tag * tag;
	NET_StreamClass stream;

    top_state = lo_FetchTopState(XP_DOCID(context));
    if (!top_state || !top_state->doc_state)
        return;
    state = top_state->doc_state;
    
    /* 
     * Remember the fake element we created but clear the current
     *   script flag of the top_state since the FlushBlockage call
     *   might just turn around and block us on another script
     */
    current_script = top_state->current_script;
    top_state->current_script = NULL;

    /* 
     * if we are finishing a nested script make sure the current_script
     *   points to our parent script
     */
    for (tag = top_state->tags; tag; tag = tag->next) {
        if (tag->type == P_NSCP_REBLOCK) {
	    top_state->current_script = tag->lo_data;
	    break;
        }
    }


    /* Flush tags blocked by this <SCRIPT SRC="URL"> tag. */
    block_ele = top_state->layout_blocking_element;

    /* no longer in a script */
    top_state->in_script = SCRIPT_TYPE_NOT;

    /* 
     * we must be blocked on something either the script that just ended or
     *   some other object
     */
    if (!block_ele)
        goto done;

    if (messWithParser && top_state->doc_data &&
        SCRIPT_EXEC_OK(top_state, state, block_ele->type, LO_SCRIPT)) {
	  top_state->doc_data->overflow_depth --;
	XP_ASSERT(top_state->doc_data->overflow_depth >= 0);
    }

	lo_FlushLineBuffer(context, top_state->doc_state);

    /* 
     * unblock on UNKNOWN's as well since that's the type for
     * style attribute scripts
     */
    if (block_ele->type == LO_SCRIPT || block_ele->type == LO_UNKNOWN) {
        lo_UnblockLayout(context, top_state);
    }
    else {
        /* 
         * we're still blocked on something else - make sure
         *   there are no reblock tags that are going to hose
         *   us.  Find the reblock tags that point to our script
         *   tag and render them hermless
         */
        PA_Tag * tag;
        for (tag = top_state->tags; tag; tag = tag->next) {
            if (tag->type == P_NSCP_REBLOCK) {
                LO_Element * lo_ele = tag->lo_data;
                if (lo_ele == current_script) {
                    tag->lo_data = NULL;
                }
            }
        }
    }

    /* free the fake element we created earlier */
    lo_FreeElement(context, current_script, FALSE);

done:
    
    /* the parser is now able to receive new data from netlib */
	stream.data_object=(pa_DocData *)top_state->doc_data;
    pa_FlushOverflow(&stream);

    /*
     * The initial call to lo_UnblockLayout() might have not torn
     *   everything down because we were waiting for the last
     *   chunk of data to come out of the doc_data->overflow_buf
     * We need to get to lo_FlushedBlockedTags() and lo_FinishLayout()
     *   at the bottom of lo_FlushBlockage().
     */
    if (!top_state->layout_blocking_element)
	lo_FlushBlockage(context, top_state->doc_state, top_state->doc_state);

}


static void
lo_script_src_exit_fn(URL_Struct *url_struct, int status, MWContext *context)
{
    lo_DestroyScriptData(url_struct->fe_data);
    NET_FreeURLStruct(url_struct);

    /*
     * If our status is negative that means there was an error and the
     *   script won't be executed.  Therefore the MochaDecoder's nesting
     *   url must be cleared, and layout must be unblocked.
     */
    if (status < 0) {
        ET_SetNestingUrl(context, NULL);
        lo_unblock_script_tag(context, TRUE);
    }
}

static void
lo_script_archive_exit_fn(URL_Struct *url_struct, int status, MWContext *context)
{
    ScriptData *data = NULL;
    char *name;
    JSPrincipals *principals;
    ETEvalStuff * stuff;

    data = (ScriptData *) url_struct->fe_data;
    stuff = (ETEvalStuff *) XP_NEW_ZAP(ETEvalStuff);
    if (!stuff)
	return;

    stuff->line_no = 1;
    stuff->scope_to = NULL;
    stuff->want_result = JS_FALSE;
    stuff->version = data->version;
    stuff->data = context;

#ifndef JAVA
    /* No Java; execute without principals. */
    if (data->buffer) {
	stuff->principals = NULL;
        ET_EvaluateScript(context, data->buffer, stuff, lo_ScriptEvalExitFn);
    }
    else {
	XP_FREE(stuff);
    }
#else
    name = data->archiveSrc ? data->archiveSrc : data->id;
    principals = LM_NewJSPrincipals(url_struct, name, data->codebase);
    if (principals != NULL) {
        char *src;
        uint srcSize;

        if (data->archiveSrc) {
            /* Extract from archive using "SRC=" value */
            src = LM_ExtractFromPrincipalsArchive(principals, data->archiveSrc, 
                                                  &srcSize);
                                                  
#ifdef JSDEBUGGER
        if( src != NULL && LM_GetJSDebugActive() )
        {
            char *path = lo_BuildJSArchiveURL(url_struct->address,
            data->archiveSrc);
            if (path)
            {
                LM_JamSourceIntoJSDebug( path, src, srcSize, context );
                XP_FREE(path);
            }
        }
#endif /* JSDEBUGGER */

            if (src == NULL) {
                /* Unsuccessful extracting from archive. Now try normal SRC= lookup. */
                (*principals->destroy)(NULL, principals);
                data->url = NET_MakeAbsoluteURL(data->state->top_state->base_url, 
                                                data->archiveSrc);
                XP_FREEIF(data->archiveSrc);
                data->archiveSrc = NULL;
                lo_GetScriptFromURL(data, data->state->top_state->in_script);
                goto out;
	    }
        } else {
            /* Should be an inline script */
            src = data->buffer;
            srcSize = data->bufferSize;
        }

	stuff->len = srcSize;
	stuff->principals = principals;
        ET_EvaluateScript(context, src, stuff, lo_ScriptEvalExitFn);

        if (data->archiveSrc){
            ET_SetNestingUrl(context, NULL);
            XP_FREE(src);
        }            
    }
    else {
	XP_FREE(stuff);
    }
	 
#endif /* ifdef JAVA */

    lo_DestroyScriptData(data);

out:
    /* Always free (or drop a ref on) the url_struct before returning. */
    NET_FreeURLStruct(url_struct);
}    

/*
 * This function gets called when the LM_EvaluateBuffer() operation
 *   completes (in the mocha thread, but we run in the mozilla thread
 *   here)
 */
void
lo_ScriptEvalExitFn(void * data, char * str, size_t len, char * wysiwyg_url, 
                    char * base_href, Bool valid)
{

    /* we don't care about the strings here just kill them */
    XP_FREEIF(str);
    XP_FREEIF(wysiwyg_url);
    XP_FREEIF(base_href);

    lo_unblock_script_tag(data, TRUE);

}

/*
 * This function gets called when the LM_EvaluateBuffer() operation
 *   completes (in the mocha thread, but we run in the mozilla thread
 *   here)
 */
PRIVATE
void
lo_StyleEvalExitFn(void * data, char * str, size_t len, char * wysiwyg_url, 
                    char * base_href, Bool valid)
{

    /* we don't care about the strings here just kill them */
    XP_FREEIF(str);
    XP_FREEIF(wysiwyg_url);
    XP_FREEIF(base_href);

    lo_unblock_script_tag(data, FALSE);

}


void
lo_UnblockLayout(MWContext *context, lo_TopState *top_state)
{
    XP_ASSERT(top_state && top_state->doc_state);
    if (!top_state || !top_state->doc_state)
        return;

    top_state->layout_blocking_element = NULL;
    lo_FlushBlockage(context, top_state->doc_state, top_state->doc_state);
}


Bool
lo_CloneTagAndBlockLayout(MWContext *context, lo_DocState *state, PA_Tag *tag)
{

    PA_Tag *new_tag;
    lo_TopState * top_state = state->top_state;

    /*
     * Since we're adding tags to the blocked tags list without going through
     * lo_BlockTag, we need to see if this is a flushed to block transition
     * and, if so, add to the doc_data's ref count. We don't want to do this
     * if we're in the process of flushing blockage - in that case, the count
     *  has already been incremented.
     */
    if (top_state->tags == NULL && top_state->flushing_blockage == FALSE)
	PA_HoldDocData(top_state->doc_data);

    /* 
     * stick the current tag onto the front of the tag list
     */
    new_tag = PA_CloneMDLTag(tag);
    if (!new_tag) {
        top_state->out_of_memory = TRUE;
        return FALSE;
    }

    new_tag->next = top_state->tags;
    top_state->tags = new_tag;
    if (top_state->tags_end == &top_state->tags)
        top_state->tags_end = &new_tag->next;

    return lo_create_script_blockage(context, state, LO_UNKNOWN);
}

/* evaluate the buffer encompassed by the style attribute on any tag.
 * this will be JSS or CSS 
 *
 * Returns TRUE if it needs to block layout
 * Returns FALSE if not.
 */
XP_Bool
lo_ProcessStyleAttribute(MWContext *context, lo_DocState *state, PA_Tag *tag, char *script_buff)
{
    lo_TopState *top_state;
    char *scope_to = NULL;
    int32 script_buff_len;
    char *new_id;
    XP_Bool in_double_quote=FALSE, in_single_quote=FALSE;
    char *ptr, *end;
    ETEvalStuff * stuff;

    if (!state)
  	{
       XP_FREEIF(script_buff);
	   return FALSE;
	}


    if (!script_buff)
        return FALSE;

    /* @@@ A bit of a hack.
     * Get rid of the style attribute in the tag->data so that we don't
     * go circular
     *
     * get rid of STYLE by rewriting it as SYTLE so that the parser 
     * ignores it :)
     */
    for (ptr=(char*)tag->data, end = ptr+tag->data_len; ptr < end; ptr++) {
        if (*ptr == '"') {
            in_double_quote = !in_double_quote;
        }
        else if (*ptr == '\'') {
            in_single_quote = !in_single_quote;
        }
        else if (!in_single_quote &&
                 !in_double_quote &&
                 !strncasecomp(ptr, PARAM_STYLE, sizeof(PARAM_STYLE)-1)) {
            *ptr = 'T';    /* ttyle NOT style */
            break;
        }
    }

    script_buff_len = XP_STRLEN(script_buff);

    top_state = state->top_state;

    /* get the tag ID if it has one */
    new_id = (char *)lo_FetchParamValue(context, tag, PARAM_ID);

    if (!new_id)
        new_id = PR_smprintf(NSIMPLICITID"%ld", top_state->tag_count);

    if (!new_id)
        return FALSE;

    if (top_state->default_style_script_type == SCRIPT_TYPE_JSSS ||
	top_state->default_style_script_type == SCRIPT_TYPE_MOCHA) {

    	StrAllocCopy(scope_to, "document.ids.");
    	StrAllocCat(scope_to, new_id);

    	if (!scope_to) {
    	    XP_FREE(new_id);
	    return(FALSE);
	}
    }
    else if (top_state->default_style_script_type == SCRIPT_TYPE_CSS) {
        char *new_jss_buff;
        int32 new_jss_buff_len;
        char *new_css_buff;
        int32 new_css_buff_len;

        /* scope the css to the ns implicit ID */
        new_css_buff = PR_smprintf("#%s { %s }", new_id, script_buff);

        XP_FREE(script_buff);

        if (!new_css_buff) {
	    XP_FREE(new_id);
	    return FALSE;
	}

        new_css_buff_len = XP_STRLEN(new_css_buff);
        
        /* convert to JSS */
        CSS_ConvertToJS(new_css_buff,
                        new_css_buff_len,
                        &new_jss_buff,
                        &new_jss_buff_len);

        XP_FREE(new_css_buff);

        if (!new_jss_buff) {
	    XP_FREE(new_id);
	    return FALSE;
	}

        script_buff = new_jss_buff;
        script_buff_len = new_jss_buff_len;
    }
    else {
        /* unknown script type, punt */
        XP_FREE(script_buff);
	XP_FREE(new_id);
        return FALSE;
    }

    /* not needed anymore */
    XP_FREE(new_id);

    stuff = (ETEvalStuff *) XP_NEW_ZAP(ETEvalStuff);
    if (!stuff) {
	/* what do we free here? */
	XP_FREE(new_id);
        return FALSE;
    }

    if (lo_CloneTagAndBlockLayout(context, state, tag)) {
        tag->type = P_UNKNOWN;
	stuff->len = XP_STRLEN(script_buff);
	stuff->line_no = top_state->script_lineno;
	stuff->scope_to = scope_to;
	stuff->want_result = JS_FALSE;
	stuff->data = context;
	stuff->version = top_state->version;
	stuff->principals = NULL;

        ET_EvaluateScript(context, script_buff, stuff, lo_StyleEvalExitFn);

	/* don't free scope_to, ET_EvaluateScript has taken possession */
        XP_FREE(script_buff);

        return TRUE;
    }

    XP_FREEIF(scope_to);
    XP_FREE(script_buff);
    
    return FALSE;
}

#ifdef DEBUG_ScriptPlugin
#include "np.h"

PA_Tag *
LO_CreateHiddenEmbedTag(PA_Tag *old_tag, char * newTagStr)
{
	PA_Tag *new_tag = PA_CloneMDLTag(old_tag);
	if(new_tag)
	{
		char * buf = NULL;
		new_tag->type = P_EMBED;
		XP_FREE(new_tag->data);
		new_tag->data = NULL;
		StrAllocCopy(buf,newTagStr);
		XP_ASSERT(buf != NULL);
		new_tag->data = (PA_Block)buf;
		new_tag->data_len = XP_STRLEN((char*)new_tag->data);
		XP_ASSERT(new_tag->data_len != 0);
	}

	return(new_tag);
}

/* Generate mimetype string corresponding to this script language.  
   Specifically, <SCRIPT language = blah> should yield a mimetype
   of "script/blah"
 */
char * npl_Script2mimeType(MWContext *context, PA_Tag *tag)
{
	PA_Block buff;
	char * language;
	char * preface = "script/";
    uint32 prefLen = XP_STRLEN(preface);
	char * mimebuf = NULL;
	uint32 len;

	buff = lo_FetchParamValue(context, tag, PARAM_LANGUAGE);
	if (buff != NULL) {
		PA_LOCK(language, char *, buff);
		len = prefLen + XP_STRLEN(language);
		mimebuf = XP_ALLOC(len+1);
		if (mimebuf)
		{
			XP_STRCPY(mimebuf, preface);
			XP_STRCAT(mimebuf, language);
		}
		PA_FREE(buff);
	}
	return mimebuf;
}
	
/* A plugin wants to handle this (otherwise unhandled) script language.
   Now we redirect it to the plugin using the following steps:

	1.  Create a cachefile w/ the appropriate extension and store the contents of the script delims
	2.  Build a hidden, semi-internal embed tag pointing to the cachefile. 
	3.  Cause Layout to initiate the hidden embed tag, which further starts netlib and other plugin
	    initialization.

 */
#define ALLOC_CHECK(a,id,abnormal_exit)   {id++; if (!a) goto abnormal_exit;}
void npl_ScriptPlugin(MWContext *context, lo_DocState *state, PA_Tag *tag, size_t line_buf_len, char * mimebuf)
{
	char * suffix				= NULL;
    char * fname				= NULL;
	char * embedtagstr			= NULL;
	char * NET_suffix			= NULL;
	PA_Tag * newTag				= NULL;
	NET_StreamClass* viewstream = NULL;
    URL_Struct* urls			= NULL;
	int streamStatus			= 0;
	int id						= 0;

	if (!context || !state || !tag || (line_buf_len <= 0) || !mimebuf){
		XP_Trace("npl_ScriptPlugin:  Invalid input(s).");
		return; /* no need to goto clean_exit, since havn't alloc'd anything */
	}

	urls = NET_CreateURLStruct("internal_url.", NET_DONT_RELOAD);
			ALLOC_CHECK(urls,id,abnormal_exit);

	/* NETLib gives me a pointer here instead of a copy of the string, so I have to make
	   my own, make checks, etc. 
	 */
	NET_suffix = NET_cinfo_find_ext(mimebuf);
	if (!NET_suffix)
		goto abnormal_exit;

	suffix = PR_smprintf("internal_url.%s", NET_suffix);
			ALLOC_CHECK(suffix,id,abnormal_exit);

			XP_FREEIF(urls->address);
	StrAllocCopy(urls->address     , suffix);
			ALLOC_CHECK(urls->address,id,abnormal_exit);

			XP_FREEIF(urls->content_name);
	StrAllocCopy(urls->content_name, suffix);
			ALLOC_CHECK(urls->content_name,id,abnormal_exit);

			XP_FREEIF(urls->content_type);
	StrAllocCopy(urls->content_type, mimebuf);
			ALLOC_CHECK(urls->content_type,id,abnormal_exit);

	urls->must_cache = 1;  	/* ugly flag for mailto and saveas */

	viewstream = NET_StreamBuilder(FO_CACHE_ONLY, urls, context);
			ALLOC_CHECK(viewstream,id,abnormal_exit);

	streamStatus =
		(*viewstream->put_block)(viewstream,(CONST char*)state->line_buf, line_buf_len);
	if (streamStatus <= 0)
		goto abnormal_exit;
	(*viewstream->complete)(viewstream);

	/* Now build internal embed tag pointing to this source:  */
    fname = WH_FileName(urls->cache_file, xpCache);
			ALLOC_CHECK(fname,id,abnormal_exit);
	embedtagstr = PR_smprintf("<EMBED SRC=file:///%s HIDDEN=TRUE>", fname);
			ALLOC_CHECK(embedtagstr,id,abnormal_exit);

	newTag = LO_CreateHiddenEmbedTag(tag,embedtagstr);
			ALLOC_CHECK(newTag,id,abnormal_exit);

	lo_FormatEmbed(context,state,newTag);	 
	goto normal_exit;

abnormal_exit:
	XP_Trace("npl_ScriptPlugin:  Abnormal termination, id = %d",id);
	if (urls) {
		XP_FREEIF(urls->content_type);
		XP_FREEIF(urls->content_name);
		XP_FREEIF(urls->address);
		XP_FREE(  urls);
	}

normal_exit:
	XP_FREEIF(newTag);
	XP_FREEIF(embedtagstr);
	XP_FREEIF(fname);
	XP_FREEIF(viewstream);
	XP_FREEIF(suffix);
	return;

}
#endif /* DEBUG_ScriptPlugin */

void
lo_ProcessScriptTag(MWContext *context, lo_DocState *state, PA_Tag *tag, JSObject *obj)
{
    lo_TopState *top_state;
    pa_DocData *doc_data;
    XP_Bool type_explicitly_set=FALSE;
    XP_Bool saw_archive=FALSE;
#ifdef DEBUG_ScriptPlugin
	char * mimebuf = NULL;
#endif

    top_state = state->top_state;
    doc_data = (pa_DocData *)top_state->doc_data;
    XP_ASSERT(doc_data != NULL || state->in_relayout || tag->lo_data);

    if (tag->is_end == FALSE) {
        PA_Block buff;
        char *str, *url, *archiveSrc, *id, *codebase;

        /* Controversial default language value. */
        top_state->version = JSVERSION_DEFAULT;
        if (tag->type == P_STYLE || tag->type == P_LINK) {
            top_state->in_script = top_state->default_style_script_type;
        }
        else {
            /* in order to get old script behaviour, pretend
             * that the content-type is explicitly set for all scripts
             */
            type_explicitly_set = TRUE;
            top_state->in_script = SCRIPT_TYPE_MOCHA;
        }

        /* XXX account for HTML comment bytes and  "lost" newlines */
        if (lo_IsAnyCurrentAncestorSourced(state))
            top_state->script_bytes = top_state->layout_bytes;
        else
            top_state->script_bytes = top_state->layout_bytes - tag->true_len;
        if (tag->lo_data != NULL) {
            top_state->script_bytes += (int32)tag->lo_data - 1;
            tag->lo_data = NULL;
        } 
        else if (doc_data != NULL) {
            top_state->script_bytes += doc_data->comment_bytes;
        } 
        else {
            XP_ASSERT(state->in_relayout);
        }

	lo_ParseScriptLanguage(context, tag, &top_state->in_script,
			       &top_state->version);
#ifdef DEBUG_ScriptPlugin
		if (top_state->in_script == SCRIPT_TYPE_UNKNOWN)
		{
			mimebuf = npl_Script2mimeType(context,tag);
			if (mimebuf){
				if (NPL_FindPluginEnabledForType(mimebuf)){
					top_state->in_script = SCRIPT_TYPE_PLUGIN;
					XP_ASSERT(top_state->mimetype == NULL);
					StrAllocCopy((char *)top_state->mimetype,mimebuf);
					XP_FREE(mimebuf);
					mimebuf = NULL;
				}
				else{
					XP_FREE(mimebuf);
					mimebuf = NULL;
				}
			}
		}
#endif /* DEBUG_ScriptPlugin */

        buff = lo_FetchParamValue(context, tag, PARAM_TYPE);
        if (buff != NULL) {
            PA_LOCK(str, char *, buff);
            if ((XP_STRCASECMP(str, js_content_type) == 0) ||
                (!XP_STRCASECMP(str, "text/javascript"))) {
		if(tag->type == P_STYLE || tag->type == P_LINK)
		{
		    top_state->in_script = SCRIPT_TYPE_JSSS;
		    top_state->default_style_script_type = SCRIPT_TYPE_JSSS;
		}
		else
		{
		    top_state->in_script = SCRIPT_TYPE_MOCHA;                    
		}
                type_explicitly_set = TRUE;
            } 
            else if ((XP_STRCASECMP(str, TEXT_CSS) == 0)) {
                top_state->in_script = SCRIPT_TYPE_CSS;
                top_state->default_style_script_type = SCRIPT_TYPE_CSS;
                type_explicitly_set = TRUE;
            } 
            else {
                top_state->in_script = SCRIPT_TYPE_UNKNOWN;
                top_state->default_style_script_type = SCRIPT_TYPE_UNKNOWN;
            }
            PA_UNLOCK(buff);
            PA_FREE(buff);
        } 

	/* check for media=screen
	 * don't load the style sheet if there 
	 * is a media not equal to screen
	 */
	buff = lo_FetchParamValue(context, tag, PARAM_MEDIA);
	if (buff) {
	    if (strcasecomp((char*)buff, "screen")) {
		/* set the script type to UNKNOWN
		 * so that it will get thrown away
		 */
		top_state->in_script = SCRIPT_TYPE_UNKNOWN;
	    }
	    PA_FREE(buff);
	}

        /*
         * Flush the line buffer so we can start storing Mocha script
         * source lines in there.
         */
        lo_FlushLineBuffer(context, state);

        url = archiveSrc = id = codebase = NULL;
        if (top_state->in_script != SCRIPT_TYPE_NOT) {
            /*
             * Check for the archive parameter for known languages.
             */
            buff = lo_FetchParamValue(context, tag, PARAM_ARCHIVE);
            if (buff != NULL) {
                saw_archive = TRUE;
                PA_LOCK(str, char *, buff);
                url = NET_MakeAbsoluteURL(top_state->base_url, str);
                PA_UNLOCK(buff);
                PA_FREE(buff);
		if (url == NULL) {
                    top_state->out_of_memory = TRUE;
                    return;
		}
            }

            /* 
             * Look for ID attribute. If it's there we have may have
             * an inline signed script.
             */
            buff = lo_FetchParamValue(context, tag, PARAM_ID);
            if (buff != NULL) {
                PA_LOCK(str, char *, buff);
                StrAllocCopy(id, str);
                PA_UNLOCK(buff);
                PA_FREE(buff);
		if (id == NULL) {
                    top_state->out_of_memory = TRUE;
		    XP_FREEIF(url);
                    return;
		}
            }

            /*
             * Now look for a SRC="url" attribute for known languages.
             * If found, synchronously load the url.
             */
	    buff = lo_FetchParamValue(context, tag, PARAM_SRC);  /* XXX overloaded rv */
            if (buff != NULL) {
		XP_Bool allowFileSrc = FALSE;
                char *absUrl;

                PA_LOCK(str, char *, buff);

		PREF_GetBoolPref(lo_jsAllowFileSrcFromNonFile, &allowFileSrc);
                absUrl = NET_MakeAbsoluteURL(top_state->base_url, str);
		if (absUrl == NULL) {
		    top_state->out_of_memory = TRUE;
		    XP_FREEIF(id);
                } else if (allowFileSrc == FALSE &&
		           NET_URL_Type(absUrl) == FILE_TYPE_URL &&
		           NET_URL_Type(top_state->url) != FILE_TYPE_URL) {
		    /*
		     * Deny access from http: to file: via SCRIPT SRC=...
		     * XXX silently
		     */
                    top_state->in_script = SCRIPT_TYPE_UNKNOWN;
                    XP_FREE(absUrl);
                    XP_FREEIF(url);
		    XP_FREEIF(id);
		} else if (url != NULL) {
                    XP_FREE(absUrl);
                    StrAllocCopy(archiveSrc, str);
		    if (archiveSrc == NULL) {
			top_state->out_of_memory = TRUE;
			XP_FREE(url);
			XP_FREEIF(id);
		    }
                } else {
                    url = absUrl;
                }
                PA_UNLOCK(buff);
                PA_FREE(buff);
		if (top_state->out_of_memory)
		    return;

                /*
                 * If we are doing a <script src=""> mocha script but JS
                 *   is turned off just ignore the tag
                 */
		if (!LM_CanDoJS(context)) {
                    top_state->in_script = SCRIPT_TYPE_UNKNOWN;
                    XP_FREE(url);
		    XP_FREEIF(id);
		    XP_FREEIF(archiveSrc);
                    return;
                }
            }
        }

        /*
         * Set text_divert so we know to accumulate text in line_buf
         * without interpretation.
         */
        state->text_divert = tag->type;

        /*
         * XXX need to stack these to handle blocked SCRIPT tags
         */
        top_state->script_lineno = tag->newline_count + 1;

        /* if we got here as a result of a LINK tag
         * check to make sure rel=stylesheet and then
         * check for an HREF and if one does not exist
         * fail
         */
        if (tag->type == P_LINK) {
            char *cbuff = (char*)lo_FetchParamValue(context, tag, PARAM_REL);
                        
            if (cbuff && !strcasecomp(cbuff, "stylesheet")) {
                XP_FREE(cbuff);

                cbuff = (char*)lo_FetchParamValue(context, tag, PARAM_HREF);

                if (cbuff) {
                    if (saw_archive && url) {
                        archiveSrc = XP_STRDUP(cbuff);
                    } else {
		        XP_FREEIF(url);
                        url = NET_MakeAbsoluteURL(top_state->base_url, cbuff);
                    }
		}
            }

            XP_FREEIF(cbuff);
        }

        if (url != NULL || id != NULL || codebase != NULL) {
            if ((doc_data != NULL) &&
                (state->in_relayout == FALSE) &&
                SCRIPT_EXEC_OK(top_state, state, tag->type, P_SCRIPT)) {
                ScriptData *data;

                data = XP_ALLOC(sizeof(ScriptData));
                if (data == NULL) {
                    top_state->out_of_memory = TRUE;
                    return;
                }
                data->context = context;
                data->state = state;
                data->tag = PA_CloneMDLTag(tag);
                if (data->tag == NULL) {
                    top_state->out_of_memory = TRUE;
		    XP_FREE(data);
                    return;
                }
                data->url = url;
                data->archiveSrc = archiveSrc;
                data->id = id;
                if (codebase == NULL) {
                    StrAllocCopy(codebase, top_state->base_url);
                }
                data->codebase = codebase;
                data->buffer = NULL;
                data->bufferSize = 0;
                data->version = top_state->version;

		/*
		 * Only SCRIPT ARCHIVE= ID= without SRC= is an inline signed
		 * script -- if there is a SRC= attribute, archiveSrc will be
		 * non-null.
		 */
                data->inlineSigned = (JSBool)
		    (url != NULL && archiveSrc == NULL && id != NULL);

                /* Reset version accumulator */
                top_state->version = JSVERSION_UNKNOWN;

                XP_ASSERT (tag->type == P_SCRIPT || tag->type == P_STYLE || 
			   tag->type == P_LINK);

	        /* 
		 * Out-of-line included (by src=) or inline signed script.
		 * Save continuatation data on top_state.  If it's signed,
		 * we'll verify the signature once we see </script> and
		 * have the inline script to verify.
		 */
		top_state->scriptData = data;

            } 
	    else {
                XP_FREE(url);
		XP_FREEIF(id);
		XP_FREEIF(archiveSrc);
            }
        }
    } 
    else {

	/*
	 * We are in the </script> tag now...
	 */

        size_t line_buf_len;
        intn script_type;
        char *scope_to=NULL;
        char *untransformed = NULL;

        script_type = top_state->in_script;
        top_state->in_script = SCRIPT_TYPE_NOT;

	/* guard against superfluous end tags */
	if (script_type == SCRIPT_TYPE_NOT)
	    goto end_tag_out;

	/* convert from CSS to JavaScript here */
        if (tag->type != P_LINK && script_type == SCRIPT_TYPE_CSS) {
            char *new_buffer;
            int32 new_buffer_length;

            CSS_ConvertToJS((char *)state->line_buf, 
                            state->line_buf_len,
                            &new_buffer,
                            &new_buffer_length);

            if (!new_buffer) {
                /* css translator error, unblock layout and return */
                state->text_divert = P_UNKNOWN;
                state->line_buf_len = 0; /* clear script text */
                goto end_tag_out;
            }

            untransformed = (char *) state->line_buf;
            state->line_buf = (PA_Block) new_buffer;
            state->line_buf_len = new_buffer_length;
            state->line_buf_size = new_buffer_length;

            if (state->line_buf_len)
                state->line_buf_len--; /* hack: subtract one to remove final \n */

            script_type = SCRIPT_TYPE_JSSS;
        }

        if (tag->type == P_STYLE) {
            /* mocha scoped to document == jsss */
            scope_to = "document";
        }

        /*
         * Reset these before potentially recursing indirectly through
         * the document.write() built-in function, which writes to the
         * very same doc_data->parser_stream that this <SCRIPT> tag
         * came in on.
         */
        state->text_divert = P_UNKNOWN;
        line_buf_len = state->line_buf_len;
        state->line_buf_len = 0;

        if (script_type != SCRIPT_TYPE_UNKNOWN && 
	    script_type != SCRIPT_TYPE_NOT) {
            /*
             * If mocha is disabled or can't be done in this context we
             *   are going to just ignore the buffer contents
             */
            if (!LM_CanDoJS(context)) {
				top_state->in_script = SCRIPT_TYPE_UNKNOWN;
                goto end_tag_out;
            }

            if ((doc_data != NULL) &&
                (state->in_relayout == FALSE) &&
                SCRIPT_EXEC_OK(top_state, state, tag->type, P_SCRIPT)) {

                /*
                 * First off, make sure layout is blocking on us
                 */
                if (lo_create_script_blockage(context, state, 
					tag->type == P_SCRIPT ? LO_SCRIPT : LO_UNKNOWN)) 
				{
                    ScriptData *data;

                    /*
                     * Extreme hackery.  Hideous and shameful.  See the comment
						* in lo_BlockScriptTag before similar is_end/overflow code
						* and commence vomiting.
                     */
                    lo_BlockScriptTag(context, state, NULL);

		    if (tag->is_end == (PRPackedBool)1) {
			  PA_PushOverflow(doc_data);
			  doc_data->overflow_depth ++;
		    }

		    /*
		     * Set the document.write tag insertion point.
		     */
             top_state->input_write_point[top_state->input_write_level] = &top_state->tags;

		    data = top_state->scriptData;
                    top_state->scriptData = NULL;
                    if (data && data->url) {
			/*
			 * Three cases:
			 * 1.  SCRIPT SRC=: url non-null
			 * 2.  SCRIPT ARCHIVE= SRC=: url, archiveSrc non-null
			 * 3.  SCRIPT ARCHIVE= ID=: url, id non-null
			 * In the last case, we copy the inline script into
			 * data's buffer and let lo_script_archive_exit_fn do
			 * the eval.  We use an inlineSigned flag to avoid a
			 * bunch of (url != NULL && archiveSrc == NULL && id
			 * != NULL) tests.
			 */
			if (data->inlineSigned) {
                            StrAllocCopy(data->buffer, (char *) state->line_buf);
                            data->bufferSize = line_buf_len;
			}
			lo_GetScriptFromURL(data, script_type);
                    }
		    else {
                        JSPrincipals *principals = NULL;
			ETEvalStuff * stuff;
			
                        if (data) {
			    principals = LM_NewJSPrincipals(NULL, data->id, 
							    data->codebase);
                            if (untransformed &&
				!LM_SetUntransformedSource(principals, 
                                                           untransformed,
                                                           (char *) state->line_buf))
                            {
                                top_state->out_of_memory = TRUE;
                            }
                            lo_DestroyScriptData(data);
			}

                        /* 
                         * send the buffer off to be evaluated 
			 */
#ifdef DEBUG_ScriptPlugin
			 			if (script_type == SCRIPT_TYPE_PLUGIN)
						{
							XP_ASSERT(mimebuf == NULL);
							npl_ScriptPlugin(context, state, tag, line_buf_len,top_state->mimetype);
						    lo_unblock_script_tag(context, TRUE);
						}
						else
#endif /* DEBUG_ScriptPlugin */
 

			stuff = (ETEvalStuff *) XP_NEW_ZAP(ETEvalStuff);
			if (!stuff)
			    goto end_tag_out;

			stuff->len = line_buf_len;
			stuff->line_no = top_state->script_lineno;
			if (scope_to)
			    stuff->scope_to = XP_STRDUP(scope_to);
			else
			    stuff->scope_to = NULL;
			stuff->want_result = JS_FALSE;
			stuff->data = context;
			stuff->version = top_state->version;
			stuff->principals = principals;

                        ET_EvaluateScript(context, 
                                          (char *) state->line_buf,
					  stuff,
                                          lo_ScriptEvalExitFn);
                    }

                    /* Reset version accumulator */
                    top_state->version = JSVERSION_UNKNOWN;
                }
            }
        }
      end_tag_out:
	/*
	 * If we got a </SCRIPT> and still have scriptData set here, it must
	 *   be left over from an error case above, so we free it.
	 */
	if (top_state->scriptData) {
	    XP_ASSERT(!top_state->layout_blocking_element);
            lo_DestroyScriptData(top_state->scriptData);
	    top_state->scriptData = NULL;
	}
        XP_FREEIF(untransformed);
    }
}

static char script_reblock_tag[]   = "<" PT_NSCP_REBLOCK ">";

/*
 * Create a tag that will reblock us
 */
void
LO_CreateReblockTag(MWContext * context, LO_Element * current_script)
{
    lo_TopState *top_state;
    pa_DocData *doc_data;
    PA_Tag * tag/*, ** tag_ptr*/;

    top_state = lo_FetchTopState(XP_DOCID(context));
    doc_data = (pa_DocData *)top_state->doc_data;

    tag = pa_CreateMDLTag(doc_data,
                          script_reblock_tag,
                          sizeof script_reblock_tag - 1);

    if (tag == NULL)
        return;

    tag->lo_data = current_script;
    
    /*
     * Kludge in the write level for sanity check in lo_LayoutTag.
     */
    tag->newline_count = (uint16)top_state->input_write_level;

    /*
     * Add the reblock tag to the tags list but don't advance the write_point
     *   in case we just wrote a nested script that also writes: the inner
     *   script must insert before the outer reblock tag, but after all the
     *   earlier tags.
     */
/*    tag_ptr = top_state->input_write_point; */
    lo_BlockTag(context, NULL, tag);
/*    top_state->input_write_point = tag_ptr; */
}
