/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
//
// $Id: streams.c,v 1.14 2006-06-12 13:41:33 hoenicke Exp $
//
// file/buffer stream functions
//
// Copyright (c) 2001 Jochen Hoenicke under Artistic License.
//
*/

#include "htp.h"
#include "snprintf.h"

BOOL CreateNullWriter (STREAM* stream) {
    stream->sflags = STREAM_FLAG_NULL_FILE;
    stream->name = "NULL";
    stream->lineNumber = 1;
    return TRUE;
}

BOOL CreateBufferWriter (STREAM* stream, const char *name) {
    stream->sflags = STREAM_FLAG_BUFFER;
    stream->u.buffer.length = 128;
    stream->u.buffer.buffer = AllocMemory(stream->u.buffer.length);
    stream->u.buffer.offset = 0;

    if (stream->u.buffer.buffer == NULL)
    {
        HtpMsg(MSG_ERROR, NULL, "unable to allocate stream buffer");
        return FALSE;
    }
    return TRUE;
}

BOOL CreateBufferReader (STREAM* stream, STREAM *writeStream)
{
    if (writeStream->sflags != STREAM_FLAG_BUFFER)
    {
        HtpMsg(MSG_ERROR, NULL, "CreateBufferReader: wrong buffer type");
        return FALSE;
    }
    stream->sflags = STREAM_FLAG_BUFFER | STREAM_FLAG_READER;
    stream->name = writeStream->name;
    stream->lineNumber = writeStream->lineNumber;
    stream->u.buffer.length = writeStream->u.buffer.offset;
    stream->u.buffer.buffer = writeStream->u.buffer.buffer;
    stream->u.buffer.offset = 0;
    stream->hasUnread = FALSE;
    return TRUE;
}

BOOL FlushBufferWriter (STREAM *stream)
{
    char *str;
    if (stream->sflags != STREAM_FLAG_BUFFER)
    {
        HtpMsg(MSG_ERROR, NULL, "CreateBufferReader: wrong buffer type");
        return FALSE;
    }
    str = AllocMemory(stream->u.buffer.offset + 1);
    memcpy(str, stream->u.buffer.buffer, stream->u.buffer.offset);
    str[stream->u.buffer.offset] = 0;
    FreeMemory(stream->u.buffer.buffer);
    stream->u.buffer.buffer = str;
    return TRUE;
}

BOOL CreateFileReader (STREAM *stream, const char *filename)
{
    stream->sflags = STREAM_FLAG_READER;
    stream->name = filename;
    stream->lineNumber = 1;
    stream->hasUnread = FALSE;
    return OpenFile(filename, "r", &stream->u.textfile);
}

BOOL CreateFDReader (STREAM *stream, const char *filename, int filedes)
{
    stream->sflags = STREAM_FLAG_READER;
    stream->name = filename;
    stream->lineNumber = 1;
    stream->hasUnread = FALSE;
    return OpenFD(filedes, "r", &stream->u.textfile);
}

BOOL CreateFileWriter (STREAM *stream, const char *filename, BOOL append)
{
    stream->sflags = 0;
    stream->name = filename;
    stream->lineNumber = 1;
    return OpenFile(filename, append ? "a" : "w", &stream->u.textfile);
}

void CloseStream (STREAM *stream)
{
    if ((stream->sflags & STREAM_FLAG_BUFFER)) {
        if (!(stream->sflags & STREAM_FLAG_READER)) {
            FreeMemory(stream->u.buffer.buffer);
            FreeMemory((void *)stream->name);
        }
    } else if ((stream->sflags & STREAM_FLAG_NULL_FILE)) {
        /* nothing */
    } else {
        CloseFile(&stream->u.textfile);
    }
}


BOOL GetStreamChar(STREAM *stream, char *c)
{
    assert(stream != NULL);
    assert((stream->sflags & STREAM_FLAG_READER));
    assert(stream->u.buffer.buffer != NULL);

    if (stream->hasUnread)
    {
        *c = stream->unread;
        stream->hasUnread = FALSE;
        return TRUE;
    }

    if ((stream->sflags & STREAM_FLAG_BUFFER))
    {
        /* Check for EOF */
        if (stream->u.buffer.length <= stream->u.buffer.offset)
            return FALSE;

        *c = stream->u.buffer.buffer[stream->u.buffer.offset++];
        if (*c == '\n')
            stream->lineNumber++;
        return TRUE;

    } else {
        BOOL result = GetFileChar(&stream->u.textfile, c);
        if (*c == '\n')
            stream->lineNumber++;
        return result;
    }
}

BOOL UnreadStreamChar(STREAM *stream, char c)
{
    assert(stream != NULL);
    assert((stream->sflags & STREAM_FLAG_READER));
    assert(stream->u.buffer.buffer != NULL);
    assert(!stream->hasUnread);

    stream->hasUnread = TRUE;
    stream->unread = c;
    return TRUE;
}

uint GetStreamBlock(STREAM *stream, char *buffer, uint size, char delim[3])
{
    char *ptr, ch;
    int read = 0;

    assert(stream != NULL);
    assert((stream->sflags & STREAM_FLAG_READER));
    assert(buffer != NULL);

    if (stream->hasUnread)
    {
        *buffer++ = ch = stream->unread;
        size--;
        stream->hasUnread = FALSE;
        if (ch == delim[0]
            || ch == delim[1])
            return 1;
        read = 1;
    }

    if ((stream->sflags & STREAM_FLAG_BUFFER))
    {
        char * end = stream->u.buffer.buffer + stream->u.buffer.length;
        char * start = stream->u.buffer.buffer + stream->u.buffer.offset;

        /* Check for EOF */
        if (start >= end)
            return FALSE;


        /* leave space for trailing NUL. */
        size--;
        if (start + size < end)
            end = start + size;

        /* Search for new line. */
        ptr = start;
        while (ptr < end)
        {
            *buffer++ = ch = *ptr++;
            if (ch == '\n')
                stream->lineNumber++;
            if (ch == delim[0]
                || ch == delim[1]
                || ch == delim[2])
                break;
        }
        *buffer = 0;
        stream->u.buffer.offset += ptr - start;
        return read + ptr - start;

    } else {

        if(feof(stream->u.textfile.file))
            return FALSE;

        ptr = buffer;
        
        /* compare size to 1 rather than 0 to leave room for trailing NUL */
        while(size-- > 1)
        {
            if(GetFileChar(&stream->u.textfile, ptr) == FALSE)
            {
                break;
            }
            ch = *ptr;

            if(ch == '\n')
            {
                stream->lineNumber++;
            }

            ptr++;
            if (ch == delim[0]
                || ch == delim[1]
                || ch == delim[2])
                break;
        }
        *ptr = 0;
        return read + ptr - buffer;
    }
}

BOOL PutStreamString(STREAM *stream, const char *str)
{
    /* check the arguments */
    assert(stream != NULL);
    assert(!(stream->sflags & STREAM_FLAG_READER));
    if ((stream->sflags & STREAM_FLAG_BUFFER))
    {
        ulong len = strlen(str);
        ulong free = stream->u.buffer.length - stream->u.buffer.offset;

        if (len > free) {
            char * newbuffer;
            while (len > free) {
                free += stream->u.buffer.length;
                stream->u.buffer.length *= 2;
            }
            newbuffer = ResizeMemory(stream->u.buffer.buffer, 
                                     stream->u.buffer.length);
            if (newbuffer == NULL) {
                /* unable to enlarge buffer area */
                HtpMsg(MSG_ERROR, NULL, "unable to grow stream buffers");
                return FALSE;
            }
            stream->u.buffer.buffer = newbuffer;
        }
        memcpy (stream->u.buffer.buffer + stream->u.buffer.offset, str, len);
        stream->u.buffer.offset += len;
        return TRUE;
    } else if ((stream->sflags & STREAM_FLAG_NULL_FILE)) {
        return TRUE;
    } else {
        return PutFileString(&stream->u.textfile, str);
    }
}

BOOL StreamPrintF(STREAM *stream, const char *format, ...)
{
    va_list argptr;
    /* reserve room to hold the final string ... allocating 4K works
     * most time.  If that isn't enough, we reallocate later. */
    char buffer[4*KBYTE];
    char *str = buffer;
    int len;


    /* convert formatted arguments into single string */
    va_start(argptr, format);
    len = vsnprintf(str, sizeof(buffer), format, argptr);
    va_end(argptr);

    if (len > sizeof(buffer) - 1) {
        str = AllocMemory(len + 1);
        va_start(argptr, format);
        len = vsnprintf(str, len + 1, format, argptr);
        va_end(argptr);
    }

    len = PutStreamString(stream, str);

    if (str != buffer)
        FreeMemory(str);

    return len;
}   


void ForceLinefeeds(STREAM *stream, BOOL forced)
{
    if (!(stream->sflags & (STREAM_FLAG_BUFFER | STREAM_FLAG_NULL_FILE)))
    {
        if (forced)
            stream->u.textfile.flags |= TEXTFILE_FLAG_FORCE_CR;
        else
            stream->u.textfile.flags &= ~TEXTFILE_FLAG_FORCE_CR;
    }
}
