/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
//
// html.c
//
// HTML markup tag functions
//
// 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 "htp.h"

const char *FindWhitespace(const char *str)
{
    assert(str != NULL);

    while(*str != NUL)
    {
        if(isspace(*str))
        {
            break;
        }
        str++;
    }

    return str;
}

const char *FindNonWhitespace(const char *str)
{
    assert(str != NULL);

    while(*str != NUL)
    {
        if(!isspace(*str))
        {
            break;
        }
        str++;
    }

    return str;
}   

/*
// HTML_ATTRIBUTE functions
*/

static BOOL MakeAttribute(HTML_ATTRIBUTE *htmlAttribute, const char *name,
                          const char *value, int quotes)
{
    assert(htmlAttribute != NULL);
    assert(name != NULL);

    /* create a duplicate of the string for the attribute structure */
    if((htmlAttribute->name = DuplicateString(name)) == NULL)
    {
        return FALSE;
    }

    /* do the same for the value, if it exists */
    if(value != NULL)
    {
        if((htmlAttribute->value = DuplicateString(value)) == NULL)
        {
            FreeMemory(htmlAttribute->name);
            htmlAttribute->name = NULL;
            return FALSE;
        }
    }
    else
    {
        htmlAttribute->value = NULL;
    }

    /* keep track if this was a quoted value */
    htmlAttribute->quotes = quotes;
    htmlAttribute->whitespace = NULL;

    return TRUE;
}   

BOOL ChangeAttributeName(HTML_ATTRIBUTE *htmlAttribute, char *name)
{
    assert(htmlAttribute != NULL);

    /* although name should never be null, let it slide */
    if(htmlAttribute->name != NULL)
    {
        FreeMemory(htmlAttribute->name);
        htmlAttribute->name = NULL;
    }

    htmlAttribute->name = name;

    return TRUE;
}

BOOL ChangeAttributeValue(HTML_ATTRIBUTE *htmlAttribute, char *value,
                          int quotes)
{
    assert(htmlAttribute != NULL);

    /* free the value's memory, if previously defined */
    if(htmlAttribute->value != NULL)
    {
        FreeMemory(htmlAttribute->value);
        htmlAttribute->value = NULL;
    }

    htmlAttribute->value = value;
    htmlAttribute->quotes = quotes;

    return TRUE;
}   

void DestroyAttribute(HTML_ATTRIBUTE *htmlAttribute)
{
    assert(htmlAttribute != NULL);
    /* the attribute should always have a name */
    assert(htmlAttribute->name != NULL);

    FreeMemory(htmlAttribute->name);
    htmlAttribute->name = NULL;

    if(htmlAttribute->value != NULL)
    {
        FreeMemory(htmlAttribute->value);
        htmlAttribute->value = NULL;
    }

    if (htmlAttribute->whitespace != NULL)
    {
        FreeMemory(htmlAttribute->whitespace);
        htmlAttribute->whitespace = NULL;
    }
}   

/*
// HTML_MARKUP functions
*/

/*
    this is perhaps the ugliest piece of code in the entire program ... it
    is also one of the most critical.  (Surprise.)

    Allan Todd provided a very important fix: htp 1.0 and before would
    crash and burn if it hit a large comment.  This was problematic for
    JavaScript and VBScript, which embed the interpreted code in comments
    to prevent other browsers from gagging.  Allans solution is simply
    to pack the entire comment into one attribute.
*/
BOOL PlaintextToMarkup(const char *plaintext, HTML_MARKUP *htmlMarkup)
{
    const char *plainPtr, *start;
    HTML_ATTRIBUTE **attribLink, *attrib;

    assert(plaintext != NULL);
    assert(htmlMarkup != NULL);

    /* although this isnt a good thing, its not something to halt execution over */
    if(plaintext[0] == NUL)
    {
        DEBUG_PRINT(("no plaintext to convert"));
        return FALSE;
    }

    plainPtr = plaintext;

    /* initialize the markup structure */
    htmlMarkup->tag = NULL;
    htmlMarkup->single = FALSE;
    htmlMarkup->attrib = NULL;
    attribLink = &htmlMarkup->attrib;

    /* walk the markup and build tag and attribute list (re-using the markup */
    /* argument to walk the copied string) */

    /* walk past any initial whitespace */
    plainPtr = FindNonWhitespace(plainPtr);
    if(*plainPtr == NUL)
    {
        return TRUE;
    }

    /* mark first token as the tag */
    start = plainPtr;

    /* walk to the first whitespace, mark it as NUL, and this is the tag */
    plainPtr = FindWhitespace(plainPtr);

    /* copy the markup tag into the structure */
    if((htmlMarkup->tag = DuplicateSubString(start, plainPtr - start)) == NULL)
    {
        DEBUG_PRINT(("unable to duplicate markup token"));
        return FALSE;
    }

    /* save and skip whitespace */
    start = plainPtr;
    plainPtr = FindNonWhitespace(plainPtr);
    htmlMarkup->whitespace = DuplicateSubString(start, plainPtr - start);

    /* if a comment or php code, throw the whole she-bang into the attribute */
    if (!strncmp(htmlMarkup->tag, "!--", 3)
        || htmlMarkup->tag[0] == '?')
    {
        if (*plainPtr == 0)
            return TRUE;
        *attribLink = attrib = AllocMemory(sizeof(HTML_ATTRIBUTE));
        if(attrib == NULL)
        {
            DEBUG_PRINT(("unable to add attribute to markup"));
            DestroyMarkupStruct(htmlMarkup);
            return FALSE;
        }
        attrib->name = DuplicateString(plainPtr);
        attrib->value = NULL;
        attrib->whitespace = NULL;
        attrib->quotes = QUOTES_NONE;
        attrib->next = NULL;
        return TRUE;
    }

    /* start walking the rest of markup, looking for attributes and their */
    /* values */
    while(*plainPtr != NUL)
    {
        /* real attribute ore xml-closing? */
        if (strcmp(plainPtr, "/") == 0) {
            htmlMarkup->single = TRUE;
            break;
        }
        
        *attribLink = attrib = AllocMemory(sizeof(HTML_ATTRIBUTE));
        if(attrib == NULL)
        {
            DEBUG_PRINT(("unable to add attribute to markup"));
            DestroyMarkupStruct(htmlMarkup);
            return FALSE;
        }
        
        /* walk through the attribute, looking for whitespace or an */
        /* equal sign */
        start = plainPtr;
        while(*plainPtr != NUL
              && *plainPtr != '='
              && !isspace(*plainPtr))
        {
            plainPtr++;
        }
        attrib->name = DuplicateSubString(start, plainPtr - start);
        attrib->value = NULL;
        attrib->quotes = QUOTES_NONE;
        attrib->next = NULL;
        attrib->whitespace = NULL;
        attribLink = &attrib->next;

        if(*plainPtr == '=')
        {
            plainPtr++;
            
            if(*plainPtr == '\"' || *plainPtr == '\'')
            {
                /* quoted value, search for end quote */
                char quote = *plainPtr;
                attrib->quotes = quote == '\"' ? QUOTES_DOUBLE: QUOTES_SINGLE;
                plainPtr++;
                start = plainPtr;
                
                while(*plainPtr != quote)
                {
                    /* ReadHtml already checked that there is
                     * a matching quote, but the quotes may
                     * have appeared at a unchecked place before.
                     */
                    if (*plainPtr == NUL) {
                        DEBUG_PRINT(("can't find matching quote in markup"));
                        DestroyMarkupStruct(htmlMarkup);
                        return FALSE;
                    }
                    plainPtr++;
                }
                attrib->value = DuplicateSubString(start, plainPtr - start);
                plainPtr++;
            }
            else
            {
                start = plainPtr;
                plainPtr = FindWhitespace(plainPtr);
                attrib->value = DuplicateSubString(start, plainPtr - start);
            }
        }
        /* skip past whitespace (looking for next value)
         */
        start = plainPtr;
        plainPtr = FindNonWhitespace(plainPtr);
        attrib->whitespace = DuplicateSubString(start, plainPtr - start);
    }

    return TRUE;
}   

BOOL AddAttributeToMarkup(HTML_MARKUP *htmlMarkup, const char *name, 
                          const char *value, int quotedValue)
{
    HTML_ATTRIBUTE **attribPtr, *attrib;
    assert(htmlMarkup != NULL);
    assert(name != NULL);

    attrib = AllocMemory(sizeof(HTML_ATTRIBUTE));
    if(attrib == NULL)
    {
        DEBUG_PRINT(("unable to create attribute structure"));
        return FALSE;
    }
    
    if (!MakeAttribute(attrib, name, value, quotedValue))
    {
        FreeMemory(attrib);
        DEBUG_PRINT(("unable to make attribute"));
        return FALSE;
    }

    attribPtr = &htmlMarkup->attrib;
    while (*attribPtr != NULL) {
        attribPtr = &(*attribPtr)->next;
    }
    *attribPtr = attrib;
    attrib->next = NULL;

    return TRUE;
}   

void DestroyMarkupStruct(HTML_MARKUP *htmlMarkup)
{
    HTML_ATTRIBUTE *attrib, *next;

    assert(htmlMarkup != NULL);

    /* destroy the tag */
    /* do not assert against this, as this function might be used to */
    /* destroy a partially-built structure */
    if(htmlMarkup->tag != NULL)
    {
        FreeMemory(htmlMarkup->tag);
        htmlMarkup->tag = NULL;
    }

    attrib = htmlMarkup->attrib;
    htmlMarkup->attrib = NULL;

    if (htmlMarkup->whitespace != NULL)
    {
        FreeMemory(htmlMarkup->whitespace);
        htmlMarkup->whitespace = NULL;
    }

    /* destroy all markup attributes */
    while (attrib != NULL) {
        next = attrib->next;
        DestroyAttribute(attrib);
        FreeMemory(attrib);
        attrib = next;
    }
}   

BOOL IsMarkupTag(HTML_MARKUP *htmlMarkup, const char *tag)
{
    assert(htmlMarkup != NULL);
    assert(tag != NULL);

    return (stricmp(htmlMarkup->tag, tag) == 0) ? TRUE : FALSE;
}   

BOOL MarkupToPlaintext(HTML_MARKUP *htmlMarkup, char **plaintext)
{
    HTML_ATTRIBUTE *htmlAttribute;
    uint size;
    uint attrSize;
    uint maxAttrSize;
    uint wslen;
    char *buffer, *bufferPtr;

    assert(htmlMarkup != NULL);
    assert(htmlMarkup->tag != NULL);
    assert(plaintext != NULL);

    /* estimate the required size of the plaintext buffer */
    maxAttrSize = 0;

    /* compute length of separating space char */
    wslen = htmlMarkup->whitespace != NULL ? 
        strlen(htmlMarkup->whitespace) : 0;
    /* force whitespace if more attributes follow */
    if (wslen == 0 && (htmlMarkup->attrib || htmlMarkup->single))
        wslen = 1;

    /* additional byte is to account for NUL */
    size = strlen(htmlMarkup->tag) + wslen + 1;
    htmlAttribute = htmlMarkup->attrib;
    while (htmlAttribute != NULL) {
        char *quotes = "";
        assert(htmlAttribute != NULL);
        assert(htmlAttribute->name != NULL);

        /* compute length of separating space char */
        wslen = htmlAttribute->whitespace != NULL ? 
            strlen(htmlAttribute->whitespace) : 0;

        /* Force whitespace if more attributes follow */
        if (wslen == 0
            && (htmlAttribute->next || htmlMarkup->single))
            wslen = 1;

        attrSize = strlen(htmlAttribute->name) + wslen;

        switch (htmlAttribute->quotes)
        {
        case QUOTES_DOUBLE:
            quotes= "\"";
            break;
        case QUOTES_SINGLE:
            quotes= "\'";
            break;
        }


        if(htmlAttribute->value != NULL)
        {
            /* additional byte added to account for equal sign */
            attrSize += strlen(htmlAttribute->value) + 1;

            /* account for the quote characters */
            attrSize += 2 * strlen(quotes);
        }

        /* additional byte added for NULL character */
        attrSize++;

        size += attrSize;
        if(maxAttrSize < attrSize)
        {
            maxAttrSize = attrSize;
        }
        htmlAttribute = htmlAttribute->next;
    }
    if (htmlMarkup->single) {
        size ++;
    }

    if((buffer = AllocMemory(size)) == NULL)
    {
        DEBUG_PRINT(("unable to allocate plaintext buffer (%u bytes)", size));
        return FALSE;
    }

    /* start copying in the markup as plaintext */
    bufferPtr = stpcpy(buffer, htmlMarkup->tag);

    if (htmlMarkup->whitespace == NULL
        || htmlMarkup->whitespace[0] == 0) {

        /* Force whitespace if more attributes follow */
        if (htmlMarkup->attrib || htmlMarkup->single)
            bufferPtr = stpcpy(bufferPtr, " ");

    } else {
        bufferPtr = stpcpy(bufferPtr, htmlMarkup->whitespace);
    }

    htmlAttribute = htmlMarkup->attrib;
    while (htmlAttribute != NULL)
    {
        char *quotes = "";

        /* checked previously, but check again */
        assert(htmlAttribute != NULL);
        assert(htmlAttribute->name != NULL);

        switch (htmlAttribute->quotes)
        {
        case QUOTES_DOUBLE:
            quotes= "\"";
            break;
        case QUOTES_SINGLE:
            quotes= "\'";
            break;
        }

        bufferPtr = stpcpy(bufferPtr, htmlAttribute->name);

        if (htmlAttribute->value != NULL)
        {
            bufferPtr = stpcpy(bufferPtr, "=");
            bufferPtr = stpcpy(bufferPtr, quotes);
            bufferPtr = stpcpy(bufferPtr, htmlAttribute->value);
            bufferPtr = stpcpy(bufferPtr, quotes);
        }

        if (htmlAttribute->whitespace == NULL
            || htmlAttribute->whitespace[0] == 0) {
            
            /* Force whitespace if more attributes follow */
            if (htmlAttribute->next || htmlMarkup->single)
                bufferPtr = stpcpy(bufferPtr, " ");
            
        } else {
            bufferPtr = stpcpy(bufferPtr, htmlAttribute->whitespace);
        }

        htmlAttribute = htmlAttribute->next;
    }

    
    if ((htmlMarkup->single) == TRUE) {
        bufferPtr = stpcpy(bufferPtr,"/");
    }

    /* give the buffer to caller */
    *plaintext = buffer;

    return TRUE;
}   

HTML_ATTRIBUTE **FindMarkupAttribute(HTML_MARKUP *htmlMarkup, const char *name)
{
    HTML_ATTRIBUTE **attribPtr;

    assert(htmlMarkup != NULL);
    assert(name != NULL);

    attribPtr = &htmlMarkup->attrib;
    while (*attribPtr != NULL) {
        if (stricmp((*attribPtr)->name, name) == 0)
            return attribPtr;
        attribPtr = &(*attribPtr)->next;
    }
    return NULL;
}   

const char *MarkupAttributeValue(HTML_MARKUP *htmlMarkup, const char *name)
{
    HTML_ATTRIBUTE **attribPtr;

    assert(htmlMarkup != NULL);

    if((attribPtr = FindMarkupAttribute(htmlMarkup, name)) == NULL)
    {
        return NULL;
    }

    /* check validity of attribute */
    assert ((*attribPtr)->name != NULL);

    return (*attribPtr)->value;
}   

BOOL ChangeMarkupTag(HTML_MARKUP *htmlMarkup, char *tag)
{
    assert(htmlMarkup != NULL);
    assert(tag != NULL);

    if(htmlMarkup->tag != NULL)
    {
        FreeMemory(htmlMarkup->tag);
    }

    htmlMarkup->tag = tag;

    return TRUE;
}

HTML_ATTRIBUTE *UnlinkAttributeInMarkup(HTML_MARKUP *htmlMarkup, 
                                        const char *name)
{
    HTML_ATTRIBUTE **attribPtr, *attrib;

    assert(htmlMarkup != NULL);

    if((attribPtr = FindMarkupAttribute(htmlMarkup, name)) == NULL)
    {
        return NULL;
    }

    attrib = *attribPtr;
    *attribPtr = attrib->next;
    attrib->next = NULL;

    return attrib;
}

BOOL UnlinkBoolAttributeInMarkup(HTML_MARKUP *htmlMarkup, const char *name)
{
    HTML_ATTRIBUTE **attribPtr, *attrib;

    assert(htmlMarkup != NULL);

    if((attribPtr = FindMarkupAttribute(htmlMarkup, name)) == NULL)
    {
        return FALSE;
    }

    attrib = *attribPtr;

    /* If it has a value it is not a bool attribute */
    if (attrib->value != NULL)
        return FALSE;

    *attribPtr = attrib->next;
    attrib->next = NULL;
    FreeMemory(attrib->name);
    if (attrib->whitespace)
        FreeMemory(attrib->whitespace);
    FreeMemory(attrib);

    return TRUE;
}   

