/***************************************************************************
 *
 * Copyright (c) 2000, 2001 BalaBit IT Ltd, Budapest, Hungary
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: readline.c,v 1.17.4.3 2001/09/19 19:22:17 bazsi Exp $
 *
 * Author  : Bazsi
 * Auditor : kisza
 * Last version: 1.20
 * Notes:
 *
 ***************************************************************************/

/* This is a big MESS, and I don't like it... Bazsi */

#include <zorp/readline.h>
#include <zorp/stream.h>
#include <zorp/log.h>

#include <assert.h>
#include <glib.h>
#include <string.h>

#define ZRL_IGNORE_TILL_EOL	0x00010000

/*+

  A ZReadLine object is wrapped around a ZStream, and can be used
  fetch complete lines.

  +*/
struct _ZReadLine
{
  ZStream *stream;
  guint flags;
  gchar *buffer;
  guint bufsize, pos, end;
  gchar *unget_line;
  guint unget_length;
};

/*+ function to fetch a line from the buffer +*/
guint
z_read_line_get_from_buf(ZReadLine *self, char **line, guint *length)
{
  guint avail = self->end - self->pos;
  char *eol = memchr(self->buffer + self->pos, '\n', avail);
  char *nul;
  
  z_enter();
  
  if (eol)
    {
      *length = eol - (self->buffer + self->pos) + 1;
      *line = self->buffer + self->pos;
      self->pos += *length;

      nul = memchr(*line, '\0', *length);
      if (nul)
        {
          if (self->flags & ZRL_NUL_NONFATAL)
            {
              *length -= eol - nul;
              z_leave();
              return ST_OK | ST_GOON;
            }
          z_leave();
          return ST_ERR | ST_GOON;
        }

      if (self->flags & ZRL_EOL_NL)
        {
          (*length)--;
        }
      else if (self->flags & ZRL_EOL_CRLF)
        {
          (*length)--;
          if (eol - self->buffer >= 1 && *(eol - 1) == '\r')
            {
              (*length)--;
            }
          else if (self->flags & ZRL_EOL_FATAL)
            {
              z_leave();
              return ST_ERR | ST_GOON;
            }
        }
      z_leave();
      return ST_OK | ST_GOON;
    }
  else if (self->pos)
    {
      *length = 0;
      memmove(self->buffer, self->buffer + self->pos, avail);
      self->end = avail;
      self->pos = 0;
    }
  z_leave();
  return ST_AGAIN | ST_GOON;
}

gboolean 
z_read_line_has_unread_data(ZReadLine *self)
{
  z_enter();
  z_leave();
  return self->unget_line || (self->end - self->pos);
}

guint
z_read_line_get_unread_data(ZReadLine *self, char *buf, guint buflen, guint *bytes_read)
{
  z_enter();
  if (z_read_line_has_unread_data(self))
    {
      guint avail;
      
      if (self->unget_line)
        {
          int add = 0;
          
          /* NOTE: this is an ugly hack, this assumes that the ungot 
           * line points to our buffer, and that the CRLF is still there
           */
          if (self->unget_line[self->unget_length] == '\n')
            add++;
          else if (self->unget_line[self->unget_length] == '\r' &&
                   self->unget_line[self->unget_length + 1] == '\n')
            add += 2;
            
          avail = MIN(self->unget_length + add, buflen);
          memcpy(buf, self->unget_line, buflen);
          *bytes_read = avail;
          if (avail >= self->unget_length)
            {
              self->unget_line = NULL;
              self->unget_length = 0;
            }
          else
            {
              self->unget_line += avail;
              self->unget_length -= avail;
            }
          z_leave();
          return ST_OK | ST_GOON;
        }
      avail = MIN(self->end - self->pos, buflen);
      memcpy(buf, self->buffer + self->pos, avail);
      self->pos += avail;
      *bytes_read = avail;
      z_leave();
      return ST_OK | ST_GOON;
    }
  *bytes_read = 0;
  z_leave();
  return ST_OK | ST_GOON;
}

/*+ 
  
  Look for a line in the input buffer, and return a reference and its
  length. The returned reference is in the input buffer, and should
  not be modified.

  Parameters:
    self      ZReadLine instance
    line      return the reference in this pointer
    length    return the length here, doesn't contain the termination
              marks (CR LF or NL)

  Returns:
    Zorp style error code.

  +*/
guint
z_read_line_get(ZReadLine *self, char **line, guint *length)
{
  guint avail, bytes_read;
  gint rc;

  z_enter();
  if (self->unget_line)
    {
      *line = self->unget_line;
      *length = self->unget_length;
      self->unget_line = NULL;
      z_leave();
      return ST_OK | ST_GOON;
    }
  if (self->end != self->pos)
    {
      /* we have something, try to return it */
      rc = z_read_line_get_from_buf(self, line, length);
      if (ST_ERROR(rc) == ST_OK)
        {
          z_leave();
          return ST_OK | ST_GOON;
        }
      /* the available data is now at the beginning of our buffer */
    }
  else
    {
      self->pos = self->end = 0;
    }

  *length = 0;
  *line = NULL;

  while (1)
    {    
      avail = self->bufsize - self->end;
      if (!avail)
        {
          /*
           * this means that there's no space in the buffer, and no eol could be
           * found
           */
          if (self->flags & ZRL_IGNORE_TILL_EOL)
            {
              self->pos = self->end = 0;
              avail = self->bufsize;
            }
          else if (self->flags & ZRL_TRUNCATE)
            {
              *line = self->buffer;
              *length = self->bufsize;
              self->pos = self->end = 0;
              self->flags |= ZRL_IGNORE_TILL_EOL;
              z_leave();
              return ST_OK | ST_GOON;
            }
          else
            {
              /*LOG
                This message is sent when the proxy reading too long
                input line. This may couse by a cracking attempt. But if
                not try to increase max_line_length.
               */
              z_log(NULL, CORE_ERROR, 4, "Too long line;");
              z_leave();
              return ST_ERR | ST_GOON;
            }
        }
        
      rc = z_stream_read(self->stream, self->buffer + self->end, avail, &bytes_read);
      switch (rc)
        {
        case G_IO_ERROR_NONE:
          if (bytes_read != 0)
    	    {
    	      self->end += bytes_read;
    	      rc = z_read_line_get_from_buf(self, line, length);
    	      if (self->flags & ZRL_IGNORE_TILL_EOL)
    	        {
    	          if (ST_ERROR(rc) == ST_OK)
    	            {
    	              self->flags &= ~ZRL_IGNORE_TILL_EOL;
                      rc = z_read_line_get_from_buf(self, line, length);
    	            }
    	          else
    	            {
    	              self->end = self->pos = 0;
    	              rc = ST_AGAIN | ST_GOON;
    	            }
    	        }
    	      if (ST_ERROR(rc) == ST_OK)
    	        {
    	          z_leave();
    	          return ST_OK | ST_GOON;
    	        }
    	        
    	      if (self->flags & ZRL_SINGLE_READ)
    	        {
    	          z_leave();
    	          return ST_AGAIN | ST_GOON;
    	        }
              break;
    	    }
          else
            {
              z_leave();
              return ST_EOF | ST_GOON;
            }
        case G_IO_ERROR_AGAIN:
          z_leave();
          return ST_AGAIN | ST_GOON;
        default:
          z_leave();
          return ST_ERR | ST_GOON;
        }
    }
}

/*+ 
  
  Fetch a line and copy it to the provided buffer.

  Parameters:
    self      ZReadLine instance
    line      pointer to the buffer
    length    the available space in buffer

  Returns:
    Zorp style error code.

  +*/
guint
z_read_line_get_copy(ZReadLine *s, char *line, guint *length)
{
  gchar *l;
  guint len;
  guint res;
  
  z_enter();
  res = z_read_line_get(s, &l, &len);
  if (ST_ERROR(res) == ST_OK)
    {
      if (len > *length)
        {
          z_leave();
          return ST_ERR | ST_GOON;
        }
      else
        {
          *length = len;
          memcpy(line, l, len);
          z_leave();
          return ST_OK | ST_GOON;
        }
    }
  z_leave();
  return res;
}


/*+

  Push back a line into the input buffer.

  Parameters:
    self      ZReadLine instance
    line      line to unget
    length    length of line to unget

  Returns:
    none

  +*/
void
z_read_line_unget(ZReadLine *self, char *line, guint length)
{
  z_enter();
  self->unget_line = line;
  self->unget_length = length;
  z_leave();
}

/*+

  Creates a new ZReadLine instance around a ZStream with the given
  buffer size and flags.

  Parameters:
    stream      Wrap around this stream, and fetch lines from this Stream.
    bufsize     Buffer size.
    flags       A combination of ZRL_ flags, which specifies line ending.

  Returns:
    ZReadLine instance

  +*/
ZReadLine *
z_read_line_new(ZStream *stream, guint bufsize, guint flags)
{
  ZReadLine *self = g_new0(ZReadLine, 1);
  
  z_enter();
  self->stream = stream;
  self->flags = flags;
  self->bufsize = bufsize;
  self->buffer = g_new(gchar, bufsize);
  g_io_channel_ref(&self->stream->super);
  z_leave();
  return self;
}


/*+

  Destroy the given ZReadLine instance.

  Parameters:
    self       ZReadLine 

  Returns:
    none

  +*/
void
z_read_line_destroy(ZReadLine *self)
{
  z_enter();
  if (self)
    {
      g_io_channel_unref(&self->stream->super);
      g_free(self->buffer);
      g_free(self);
    }
  z_leave();
}

