/*							             HTConlen.c
**	CONTENT LENGTH COUNTER STREAM
**
**	(c) COPYRIGHT MIT 1995.
**	Please first read the full copyright statement in the file COPYRIGH.
**	@(#) $Id: HTConLen.c,v 2.18 1998/05/04 19:36:23 frystyk Exp $
**
**	This stream counts the number of bytes in a stream until it reaches
**	max number of bytes it can occupy. If this happens then	it gives up.
*/

/* Library include files */
#include "wwwsys.h"
#include "WWWUtil.h"
#include "WWWCore.h"
#include "HTConLen.h"					 /* Implemented here */

#define HT_MIN_BLOCK 	0x100
#define HT_MAX_BLOCK	0x2000
#define HT_MAX_SIZE	0x10000
#define PUTBLOCK(b, l)	(*me->target->isa->put_block)(me->target,(b),(l))

typedef struct _HTBufItem {
    int			len;
    char *		buf;
    struct _HTBufItem *	next;
} HTBufItem;

typedef enum _BufferMode {
    HT_BM_PLAIN		= 0x0,
    HT_BM_DELAY		= 0x1,		/* Buffer full and we pause */
    HT_BM_COUNT		= 0x2,		/* Content length counter */
    HT_BM_PIPE		= 0x4		/* Pipe buffer */
} BufferMode;

typedef enum _BufferState {
    HT_BS_OK		= 0,
    HT_BS_PAUSE		= 1,
    HT_BS_TRANSPARENT	= 2
} BufferState;

struct _HTStream {
    HTStreamClass *	isa;
    HTRequest *		request;
    HTStream *		target;

    char *		tmp_buf;
    int			tmp_ind;
    int			tmp_max;
    HTBufItem *		head;
    HTBufItem *		tail;

    int			max_size;
    int			cur_size;
    int			conlen;
    BufferMode		mode;			   /* State of the buffering */
    BufferState		state;
};

/* ------------------------------------------------------------------------- */

/*
**	MIME output with content-length calculation
**	-------------------------------------------
**	This stream also buffers the result to find out the content length.
**	If a maximum buffer limit is reached Content-Length is calculated
**	for logs but it is not sent to the client -- rather the buffer is
**	flushed right away.
**	Code taken from HTRequest.c written by Ari Luotonen and modified to
**	fit new stream model
*/

PRIVATE BOOL free_buf (HTBufItem * me)
{
    if (me) {
	HT_FREE(me->buf);
	HT_FREE(me);
	return YES;
    }
    return NO;
}

PRIVATE void free_buf_all (HTStream * me)
{
    HTBufItem * cur = me->head;
    HTBufItem * killme;
    me->tmp_ind = 0;
    me->tmp_max = 0;
    HT_FREE(me->tmp_buf);
    while (cur) {
	killme = cur;
	cur = cur->next;
	free_buf(cur);
    }
    me->head = me->tail = NULL;
}

PRIVATE void append_buf (HTStream * me)
{
    HTBufItem * b;
    if ((b = (HTBufItem  *) HT_CALLOC(1, sizeof(HTBufItem))) == NULL)
        HT_OUTOFMEM("append_buf");
    b->len = me->tmp_ind;
    b->buf = me->tmp_buf;
    me->tmp_ind = 0;
    me->tmp_max = 0;
    me->tmp_buf = 0;
    if (me->tail)
	me->tail->next = b;
    else
	me->head = b;
    me->tail = b;
}

PRIVATE BOOL alloc_new (HTStream * me, int size)
{
    if (me->conlen >= me->max_size) {
	if (STREAM_TRACE)
	    HTTrace("Buffer...... size %d reached, going transparent\n",
		    me->max_size);
	return NO;
    } else if (size) {
	me->tmp_ind = 0;
	me->tmp_max = size;
	if ((me->tmp_buf = (char  *) HT_MALLOC(size)) == NULL)
	    HT_OUTOFMEM("buf_put_char");
	if (STREAM_TRACE)
	    HTTrace("Buffer...... created with len %d\n", size);
	return YES;
    }
    return NO;
}

/*
**	After flushing the buffer we go into transparent mode so that we still
**	can handle incoming data. If we already are in transparent mode then
**	don't do anything.
*/
PRIVATE int buf_flush (HTStream * me)
{
    if (me->state != HT_BS_TRANSPARENT) {
	HTBufItem * cur;
	if (me->tmp_buf) append_buf(me);
	while ((cur = me->head)) {
	    int status;
	    if ((status = PUTBLOCK(cur->buf, cur->len)) != HT_OK) {
		return status;
	    }
	    me->head = cur->next;
	    free_buf(cur);
	}

	/* If we are not a pipe then do no more buffering */
	if (!(me->mode & HT_BM_PIPE)) me->state = HT_BS_TRANSPARENT;
    }
    return (*me->target->isa->flush)(me->target);
}

PRIVATE int buf_put_block (HTStream * me, const char * b, int l)
{
    /*
    **  If we are in pause mode then don't write anything but return PAUSE.
    **  The upper stream should then respect it and don't write any more data.
    */
    if (me->state == HT_BS_PAUSE) return HT_PAUSE;

    /*
    **  Start handling the incoming data. If we are still buffering then add
    **  it to the buffer. Otherwise just pump it through. Note that we still
    **  count the length - even if we have given up buffering!
    */
    me->conlen += l;
    if (me->state != HT_BS_TRANSPARENT) {

	/*
	**  If there is still room in the existing chunk then fill it up.
	**  Otherwise create a new chunk and add it to the linked list of
	**  chunks. If the buffer fills up then either return HT_PAUSE or
	**  flush it and go transparent.
	*/
	if (me->tmp_buf && me->tmp_max-me->tmp_ind >= l) {     /* Still room */
	    memcpy(me->tmp_buf + me->tmp_ind, b, l);
	    me->tmp_ind += l;
	    return HT_OK;
	} else {

	    /*
	    **  Add the temporary buffer (if any) to the list of chunks
	    */
	    if (me->tmp_buf) append_buf(me);

	    /*
	    **  Find the right size of the next chunk. We increase the size
	    **  exponentially until we reach HT_MAX_BLOCK in order to minimize
	    **  the number of mallocs.
	    */
	    if (me->cur_size < HT_MAX_BLOCK) {
		int newsize = me->cur_size ? me->cur_size : HT_MIN_BLOCK;
		while (l > newsize && newsize < HT_MAX_BLOCK) newsize *= 2;
		me->cur_size = newsize;
	    }

	    if (alloc_new(me, me->cur_size)) {
		/* Buffer could accept the new data */
		memcpy(me->tmp_buf, b, l);
		me->tmp_ind = l;
	    } else if (me->mode & HT_BM_DELAY) {
		/* Buffer ran full and we pause */
		me->state = HT_BS_PAUSE;
		if (STREAM_TRACE) HTTrace("Buffer....... Paused\n");
		return HT_PAUSE;
	    } else {
		/* Buffer ran full and we flush and go transparent */
		int status = buf_flush(me);
		if (status != HT_OK) return status;
	    }
	}
    }

    /*
    **  If we couldn't buffer the data then check whether we should give up
    **  or pause the stream. If we are in transparent mode then put the rest
    **  of the data down the pipe.
    */
    if (me->state == HT_BS_TRANSPARENT) return PUTBLOCK(b, l);
    return HT_OK;
}

PRIVATE int buf_put_char (HTStream * me, char c)
{
    return buf_put_block(me, &c, 1);
}

PRIVATE int buf_put_string (HTStream * me, const char * s)
{
    return buf_put_block(me, s, (int) strlen(s));
}

PRIVATE int buf_free (HTStream * me)
{
    int status = HT_OK;

    /*
    **  If the buffer has not been flushed explicit and we are a pipe buffer
    **  then we don't free it.
    */ 
    if (me->mode & HT_BM_PIPE && me->state != HT_BS_TRANSPARENT) {
	if (STREAM_TRACE) HTTrace("PipeBuffer Waiting to be flushed\n");
	return HT_OK;
    }

    /*
    **  Should we count the content length and assign it to the
    **  anchor?
    */
    if (me->mode & HT_BM_COUNT && me->request) {
	HTParentAnchor * anchor = HTRequest_anchor(me->request);
	if (STREAM_TRACE)
	    HTTrace("Buffer........ Calculated content-length: %d\n", me->conlen);
	HTAnchor_setLength(anchor, me->conlen);
    }

    /*
    **  Flush the buffered data - even if we are paused. That is, we always
    **  flush - except if we have already flushed the buffer, of course.
    **  Also, we don't flush if we are a PIPE buffer. Then the flush MUST be
    **  called explicitly and the buffer can not be freed before this 
    **  happens.
    */
    if ((status = buf_flush(me)) != HT_OK)
	return status;
    if ((status = (*me->target->isa->_free)(me->target)) != HT_OK)
	return status;
    HT_FREE(me);
    return status;
}

PRIVATE int buf_abort (HTStream * me, HTList * e)
{
    if (me->target) (*me->target->isa->abort)(me->target,e);
    free_buf_all(me);
    HT_FREE(me);
    if (PROT_TRACE) HTTrace("Buffer...... ABORTING...\n");
    return HT_ERROR;
}

HTStreamClass HTBufferClass = {
    "Buffer",
    buf_flush,
    buf_free,
    buf_abort,
    buf_put_char,
    buf_put_string,
    buf_put_block
};

PUBLIC HTStream * HTBuffer_new (HTStream *	target,
				HTRequest *	request,
				int		max_size)
{
    HTStream * me;
    if ((me = (HTStream  *) HT_CALLOC(1, sizeof(HTStream))) == NULL)
        HT_OUTOFMEM("HTBufferStream");
    me->isa = &HTBufferClass;
    me->target = target;
    me->request = request;
    me->max_size = (max_size > 0) ? max_size : HT_MAX_SIZE;
    me->mode = HT_BM_PLAIN;
    if (STREAM_TRACE) HTTrace("Buffer...... Created with size %d\n", me->max_size);
    return me;
}

PUBLIC HTStream * HTDelayBuffer (HTStream * target, int max_size)
{
    HTStream * me = HTBuffer_new(target, NULL, max_size);
    if (me) {
	me->mode = HT_BM_DELAY;
	return me;
    }
    return HTErrorStream();
}

PUBLIC HTStream * HTContentCounter (HTStream *	target,
				    HTRequest *	request,
				    int		max_size)
{
    HTStream * me = HTBuffer_new(target, NULL, max_size);
    if (me) {
	me->mode = HT_BM_COUNT;
	return me;
    }
    return HTErrorStream();
}

PUBLIC HTStream * HTPipeBuffer (HTStream * target, int max_size)
{
    HTStream * me = HTBuffer_new(target, NULL, max_size);
    if (me) {
	me->mode = HT_BM_PIPE;
	return me;
    }
    return HTErrorStream();
}
