/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
//
// htp-files.c
//
// htp specific file access
//
// 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 "defs.h"
#include "option.h"
#include "streams.h"
#include "bool-proc.h"
#include "snprintf.h"

/*
// returns the full, qualified pathname of the default htp include file
//
// Returns FALSE if unable to find the file.
*/
BOOL HtpDefaultFilename(char *filename, uint size)
{
    char *defFile;

    /* get the name of the default file from the HTPDEF environement */
    /* variable */
    if((defFile = getenv("HTPDEF")) == NULL)
    {
        return FALSE;
    }

    /* verify that the file exists */
    if(FileExists(defFile) == FALSE)
    {
        return FALSE;
    }

    /* copy the filename into the buffer and skeedaddle */
    StringCopy(filename, defFile, size);

    return TRUE;
}

/*
// compare files modified time/date stamp, as a dependency check ... returns
// TRUE if the dependency does not require an update, FALSE otherwise (which
// could either be a timestamp discrepency, or simply that the resulting file
// does not exist) ... if dependency checking is turned off, this function
// will always return FALSE.
//
// Returns ERROR if dependency file does not exist.
*/
BOOL IsTargetUpdated(const char *dependency, const char *target)
{
    struct stat dependStat;
    struct stat targetStat;
    char *dependName;
    char *targetName;

    assert(dependency != NULL);
    assert(target != NULL);

    /* always update targets? */
    if(DEPEND == FALSE)
    {
        return FALSE;
    }

    /* convert the dependency and target filenames for this filesystem */
    if((dependName = ConvertDirDelimiter(dependency)) == NULL)
    {
        return ERROR;
    }

    if((targetName = ConvertDirDelimiter(target)) == NULL)
    {
        FreeMemory(dependName);
        return ERROR;
    }

    /* get information on the dependency file */
    if(stat(dependName, &dependStat) != 0)
    {
        /* dependency file needs to exist */
        FreeMemory(dependName);
        FreeMemory(targetName);

        return ERROR;
    }

    /* get information on the target file */
    if(stat(targetName, &targetStat) != 0)
    {
        /* target file does not exist, dependency needs to be updated */
        FreeMemory(dependName);
        FreeMemory(targetName);

        return FALSE;
    }

    FreeMemory(dependName);
    FreeMemory(targetName);

    /* compare modification times to determine if up-to-date */
    return (dependStat.st_mtime <= targetStat.st_mtime) ? TRUE : FALSE;
}

/*
// searches for the specified file in the search path ... this function is
// very stupid, it simply gets the first directory in the search string,
// appends the file directly to the end, and tests for existance.  Repeat.
*/
BOOL SearchForFile(const char *filename, char *fullPathname, uint size)
{
    char *searchPathCopy;
    char *ptr;
    char *convertedName;
    FIND_TOKEN findToken;

    /* quick check for search path even being defined */
    if(searchPath[0] == NUL)
    {
        return FALSE;
    }

    /* need to make a copy of the search path for String...Token() to butcher up */
    if((searchPathCopy = DuplicateString(searchPath)) == NULL)
    {
        printf("%s: unable to allocate temporary buffer for include path (out of memory?)\n",
            PROGRAM_NAME);
        return FALSE;
    }

    /* look for ';' delimiter */
    ptr = StringFirstToken(&findToken, searchPathCopy, ";");
    while(ptr != NULL)
    {
        StringCopy(fullPathname, ptr, size);

        /* if the last character is not a directory delimiter, add it */
        if(strchr(ALL_FILESYSTEM_DELIMITERS, fullPathname[strlen(fullPathname) - 1]) == NULL)
        {
            strncat(fullPathname, DIR_DELIMITER_STRING, size);
        }

        /* append the file name */
        strncat(fullPathname, filename, size);

        /* need to do a complete conversion of delimiters in the filename, but */
        /* ConvertDirDelimiter() returns a AllocMemory()'d copy of the string ... */
        convertedName = ConvertDirDelimiter(fullPathname);

        /* check for existance */
        if(FileExists(convertedName) == TRUE)
        {
            /* clean up and get outta here */
            StringCopy(fullPathname, convertedName, size);

            FreeMemory(searchPathCopy);
            FreeMemory(convertedName);

            return TRUE;
        }

        FreeMemory(convertedName);
        convertedName = NULL;

        ptr = StringNextToken(&findToken);
    }

    /* clean up */
    FreeMemory(searchPathCopy);

    return FALSE;
}

static char *markupBuffer = NULL;
static uint markupSize = 0;

static void FreeMarkupBuffer(void) {
    FreeMemory(markupBuffer);
    markupBuffer = NULL;
}

/*
// TRUE = plaintext is filled with new plain text markup, FALSE if end of file,
// ERROR if a problem
// !! Don't like using ERROR in any BOOL return values
*/
BOOL ReadHtmlMarkup(STREAM *infile, STREAM *outfile, 
                    uint *markupType, HTML_MARKUP *htmlMarkup)
{
    char ch;
    uint ctr;
    char inQuotes;
    uint startLine;
    uint numread;
    char open_markup[3];
    char tag_specials[3];

    open_markup[0] = HTML_OPEN_MARKUP;
    open_markup[1] = htpOpenMarkup;
    open_markup[2] = htpOpenMarkup;

    tag_specials[1] = '\"';
    tag_specials[2] = '\'';
#if 0
    memset(open_markup, 0, sizeof(open_markup));
    BITMAP_SET(open_markup, HTML_OPEN_MARKUP);
    BITMAP_SET(open_markup, htpOpenMarkup);

    memset(tag_specials, 0, sizeof(tag_specials));
    BITMAP_SET(tag_specials, HTML_CLOSE_MARKUP);
    BITMAP_SET(tag_specials, htpCloseMarkup);
    BITMAP_SET(tag_specials, '\"');
    BITMAP_SET(tag_specials, '\'');
#endif

    assert(infile != NULL);

    /* if outfile is NULL, then the input stream is just being walked and */
    /* not parsed for output ... i.e., don't assert outfile != NULL */

    /* allocate some space for markup plaintext ... this will dynamically */
    /* expand if necessary, and has to be freed by the caller */
    if(markupBuffer == NULL)
    {
        if ((markupBuffer = AllocMemory(MIN_PLAINTEXT_SIZE)) == NULL)
        {
            HtpMsg(MSG_ERROR, NULL, 
                   "unable to allocate memory to read HTML file");
            return ERROR;
        }
        /* track the markupBuffer size */
        markupSize = MIN_PLAINTEXT_SIZE;
        atexit(FreeMarkupBuffer);
    }

    for (;;)
    {
        numread = GetStreamBlock(infile, markupBuffer, 
                                 markupSize, open_markup);
        if (numread == 0)
        {
            /*EOF*/
            /* no markup found, end of file */
            
            return FALSE;
        }

        if (IS_OPEN_MARKUP(markupBuffer[numread-1]))
        {
            /* get the type of markup for caller */
            *markupType = MarkupType(markupBuffer[numread-1]);
            if (numread > 1 && outfile != NULL) {
                /* there is normal text, just copy it to the output file */
                markupBuffer[numread-1] = NUL;
                StreamPrintF(outfile, "%s", markupBuffer);
            }
            break;
        }

        /* no open markup found in 2KB.  Print the full block and
         * continue.  
         */
        if (outfile != NULL)
            StreamPrintF(outfile, "%s", markupBuffer);
    }


    /* copy the markup into the markupBuffer */
    ctr = 0;
    inQuotes = NUL;
    startLine = infile->lineNumber;
    tag_specials[0] = MARKUP_CLOSE_DELIM(*markupType);
    for(;;)
    {
        numread = GetStreamBlock(infile, markupBuffer + ctr, 
                                 markupSize - ctr, tag_specials);
        if (numread == 0)
        {
            /* EOF ... this is not acceptable before the markup is */
            /* terminated */
            infile->lineNumber = startLine;
            HtpMsg(MSG_ERROR, infile,
                   "markup tag is never closed", startLine);
            
            return ERROR;
        }
        
        ctr += numread;
        ch = markupBuffer[ctr - 1];
        if ((IS_CLOSE_MARKUP(ch))
            && (MarkupType(ch) == *markupType))
        {
            /* Check whether this end tag closes the tag.  We need
             * special checks for PHP-tags and comment tags
             */
            if (strncmp(markupBuffer, "!--", 3) == 0 
                /* comment tag: check for -- */
                ? strncmp(markupBuffer+ctr-3, "--", 2) == 0
                /* PHP tag: check for ? */
                : markupBuffer[0] == '?' ? markupBuffer[ctr-2] == '?'
                /* normal tag: check for matching quotes */
                : inQuotes == NUL)
            {

                /* end of markup, terminate string and exit */
                markupBuffer[ctr - 1] = NUL;
                break;
            }
        }
        
        /* track quotation marks ... can only close markup when */
        /* all quotes have been closed */
        if(inQuotes)
        {
            if (ch == inQuotes)
                inQuotes = NUL;
        }
        else if (ch == '\'' || ch == '\"')
        {
            inQuotes = ch;
        }
        
        /* check for overflow ... resize markupBuffer if necessary */
        if(ctr >= markupSize - 1)
        {
            char *newmarkupBuffer = 
                ResizeMemory(markupBuffer, markupSize + PLAINTEXT_GROW_SIZE);
            if(newmarkupBuffer == NULL)
            {
                /* unable to enlarge markupBuffer area */
                HtpMsg(MSG_ERROR, NULL,
                       "unable to reallocate memory for reading HTML file");
                return ERROR;
            }
                
            markupBuffer = newmarkupBuffer;
            markupSize += PLAINTEXT_GROW_SIZE;
        }
    }
        
    if(PlaintextToMarkup(markupBuffer, htmlMarkup) == FALSE)
    {
        HtpMsg(MSG_ERROR, infile,
               "could not parse markup tag (out of memory?)");
        return ERROR;
    }

    return TRUE;
}   

BOOL FullyCheckDependencies(const char *in, const char *out)
{
    BOOL result;
    STREAM infile;
    char title[MAX_PATHNAME_LEN];
    HTML_MARKUP markup;
    const char *includeFile;
    const char *imageFile;
    BOOL readResult;
    BOOL checkResult;
    uint markupType;

    assert(in != NULL);
    assert(out != NULL);

    if(DEPEND == FALSE)
    {
        /* outta here */
        return FALSE;
    }

    /* check if target file is completely up to date compared to input file */
    result = IsTargetUpdated(in, out);
    if(result == ERROR)
    {
        printf("%s: unable to get file information for file \"%s\"\n",
            PROGRAM_NAME, in);
        return ERROR;
    }
    else if(result == FALSE)
    {
        /* target is not updated */
        return FALSE;
    }

    /* because target is up to date, need to search dependency file for */
    /* FILE INCLUDE tags and check those files likewise */

    /* open file */
    snprintf(title, sizeof(title), "Dependency check for %s", in);
    if (CreateFileReader(&infile, in) == FALSE)
    {
        printf("%s: unable to open file \"%s\" for reading while checking dependencies\n",
            PROGRAM_NAME, in);
        return ERROR;
    }
    infile.name = title;

    /* assume everything is hunky-dory unless otherwise discovered */
    checkResult = TRUE;

    /* get the next markup tag from the input file */
    while((readResult = ReadHtmlMarkup(&infile, NULL, 
                                       &markupType, &markup)) != FALSE)
    {
        if(readResult == ERROR)
        {
            /* error occurred processing the HTML file */
            checkResult = ERROR;
            break;
        }

        /* check markup type ... only interested in htp markups currently */
        if((markupType & MARKUP_TYPE_HTP) == 0)
        {
            DestroyMarkupStruct(&markup);
            continue;
        }

        /* if FILE INCLUDE markup, get the filename specified */
        includeFile = NULL;
        imageFile = NULL;
        if(IsMarkupTag(&markup, "FILE"))
        {
            if(MarkupAttributeValue(&markup, "EXECUTE") != NULL)
            {
                /* If we find an execute tag, we can't be sure that it
                 * is up to date */
                result = FALSE;
                DestroyMarkupStruct(&markup);
                break;
            }
            includeFile = MarkupAttributeValue(&markup, "INCLUDE");
            if (includeFile == NULL)
            {
                includeFile = MarkupAttributeValue(&markup, "TEMPLATE");
            }
        }
        else if(IsMarkupTag(&markup, "IMG"))
        {
            imageFile = MarkupAttributeValue(&markup, "SRC");
        }
        else if(IsMarkupTag(&markup, "OPT"))
        {
            if(UnlinkBoolAttributeInMarkup(&markup, "NODEPEND"))
            {
                /* !! dependency checking disabled in source file ... since this */
                /* can swing back and forth throughout the files, and its just */
                /* a pain to track what is technically the last one set, */
                /* if one is found, dependency checking is disabled and the */
                /* targets are not considered updated */
                /* this could be fixed with some work */
                checkResult = FALSE;
                DestroyMarkupStruct(&markup);
                break;
            }
        }

        /* by default assume everything is up to date unless more information */
        /* is available through other files */
        result = TRUE;

        /* check include or image file timestamps */
        if(includeFile != NULL)
        {
            /* !! the accursed recursion strikes again */
            /* check the dependencies based on this new file */
            result = FullyCheckDependencies(includeFile, out);
        }
        else if(imageFile != NULL)
        {
            /* check the image files timestamp as part of dependency checking */
            if(FileExists(imageFile))
            {
                result = IsTargetUpdated(imageFile, out);
            }
        }

        /* unneeded now */
        DestroyMarkupStruct(&markup);

        if(result != TRUE)
        {
            /* if FALSE, not up to date, no need to go further */
            /* if ERROR, need to stop and report to caller */
            checkResult = result;
            break;
        }

        /* otherwise, TRUE indicates that everything is okay, */
        /* so keep searching */
    }

    /* EOF encountered in the HTML input file ... target is updated */
    CloseStream(&infile);

    return checkResult;
}
