/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
//
// bool-proc.c
//
// specialized markup processors
//
// Copyright (c) 2002-2003 Jochen Hoenicke.
// Copyright (c) 1995-96 Jim Nelson.  Permission to distribute
// granted by the author.  No warranties are made on the fitness of this
// source code.
//
*/

#include "bool-proc.h"

#include "defs.h"
#include "htp-files.h"


static BOOL EvaluateIfTag(TASK *task, HTML_MARKUP *htmlMarkup)
{
    BOOL condTrue, notTagFound;
    HTML_ATTRIBUTE *attrib;
    const char *value;
    uint type;

    /* this is an ugly way to handle the IF-IF NOT test, but will need */
    /* be cleaned up in the future */
    
    notTagFound = UnlinkBoolAttributeInMarkup(htmlMarkup, "NOT");
            
    /* should either be one or two attributes in markup */
    if(htmlMarkup->attrib == NULL)
    {
        HtpMsg(MSG_ERROR, task->infile, "no conditional to test");
        return MARKUP_ERROR;
    }

    if(htmlMarkup->attrib->next != NULL)
    {
        HtpMsg(MSG_ERROR, task->infile, 
               "too many items in conditional expression");
        return MARKUP_ERROR;
    }

    attrib = htmlMarkup->attrib;

    /* get the macros associated type (UNKNOWN if macro is not defined) */
    type = GetVariableType(task->varstore, attrib->name);

    /* if only a name is specified, only care if macro is defined */
    if(attrib->value == NULL)
    {
        condTrue = (type != VAR_TYPE_UNKNOWN) ? TRUE : FALSE;
    }
    else
    {
        /* macro value comparison */
        if(type == VAR_TYPE_SET_MACRO)
        {
            /* macro comparison (case-sensitive) */
            value = GetVariableValue(task->varstore, attrib->name);
            condTrue = (value && strcmp(value, attrib->value) == 0)
                ? TRUE : FALSE;
        }
        else
        {
            /* block macro, comparisons not allowed */
            condTrue = FALSE;
        }
    }

    /* reverse conditional if NOT attribute found */
    return condTrue != notTagFound;
}

static BOOL DiscardConditionalBlock(TASK *task, BOOL takeElseBlock)
{
    HTML_MARKUP htmlMarkup;
    BOOL result;
    uint embeddedConditionals;
    uint markupType;
    char ch;

    /* discard the block, looking for the matching ELSE or /IF statement */
    embeddedConditionals = 0;
    for(;;)
    {
        result = ReadHtmlMarkup(task->infile, NULL, &markupType, &htmlMarkup);
        if(result == ERROR)
        {
            return FALSE;
        }
        else if(result == FALSE)
        {
            /* end-of-file before end-of-conditional ... error */
            HtpMsg(MSG_ERROR, task->infile,
                   "Missing </IF> for <IF> in line %d",
                   task->conditionalLine[task->conditionalLevel-1]);
            return FALSE;
        }

        /* another conditional started? */
        if(IsMarkupTag(&htmlMarkup, "IF"))
        {
            embeddedConditionals++;
        }
        else if(IsMarkupTag(&htmlMarkup, "/IF"))
        {
            /* end of the conditional? */
            if(embeddedConditionals == 0)
            {
                DestroyMarkupStruct(&htmlMarkup);
                task->conditionalLevel--;
                break;
            }
            embeddedConditionals--;
        }
        else if(embeddedConditionals == 0 && takeElseBlock)
        {
            /* Check whether this is an ELSE or a taken ELSEIF tag. */
            if (IsMarkupTag(&htmlMarkup, "ELSE")
                || (IsMarkupTag(&htmlMarkup, "ELSEIF")
                    && EvaluateIfTag(task, &htmlMarkup) == TRUE))
            {
                DestroyMarkupStruct(&htmlMarkup);

                /* Skip EOL, but put it back if it's another character */
                if (GetStreamChar(task->infile, &ch) && ch != '\n')
                {
                    UnreadStreamChar(task->infile, ch);
                }
                break;
            }
        }

        /* destroy and continue */
        DestroyMarkupStruct(&htmlMarkup);
    }

    return TRUE;
}   


uint BooleanProcessor(TASK *task, HTML_MARKUP *htmlMarkup, char **newPlaintext)
{
    char ch;
    UNREF_PARAM(newPlaintext);

    /* conditionalLevel keeps track of boolean depth */
    if(task->conditionalLevel == 0
       && !IsMarkupTag(htmlMarkup, "IF"))
    {
        HtpMsg(MSG_ERROR, task->infile, 
               "<%s> without matching IF tag", htmlMarkup->tag);
        return MARKUP_ERROR;
    }

    if(IsMarkupTag(htmlMarkup, "IF"))
    {
        if (task->conditionalLevel >= MAX_CONDITIONAL_LEVEL)
        {
            HtpMsg(MSG_ERROR, task->infile, "Too many nested <IF> tags");
            return MARKUP_ERROR;
        }
        task->conditionalLine[task->conditionalLevel++]
            = task->infile->lineNumber;

        if (EvaluateIfTag(task, htmlMarkup) == FALSE)
        {

            /* discard the rest of the conditional block since this
             * portion has evaluated false.  We still can take the
             * ELSE, though
             */
            if(DiscardConditionalBlock(task, TRUE) == FALSE)
            {
                return MARKUP_ERROR;
            }
        }
        else
        {
            /* Skip EOL, but put it back if it's another character */
            if (GetStreamChar(task->infile, &ch) && ch != '\n')
            {
                UnreadStreamChar(task->infile, ch);
            }
        }
    }
    else if (IsMarkupTag(htmlMarkup, "ELSE")
             || IsMarkupTag(htmlMarkup, "ELSEIF"))
    {
        /* this can only occur if the associated conditional statement */
        /* evaluated TRUE, so the remaining block must be discarded */
        /* We must not even take the ELSE part */
        if(DiscardConditionalBlock(task, FALSE) == FALSE)
        {
            return MARKUP_ERROR;
        }
    }
    else
    {
        /* end of conditional */
        assert(task->conditionalLevel > 0);
        task->conditionalLevel--;
    }

    return DISCARD_MARKUP;
}   
