#include <ctype.h>
#include <string.h>
#include "gskhttpserver.h"
#include "../gskmacros.h"

static GObjectClass *parent_class = NULL;

/* forward declarations for the "POST-stream" */
typedef struct _GskHttpServerPostStream GskHttpServerPostStream;
static GskHttpServerPostStream *
gsk_http_server_post_stream_new (GskHttpServer         *server,
				 gboolean               is_chunked,
				 gint                   length);
static gboolean
gsk_http_server_post_stream_process (GskHttpServerPostStream *post_stream);
static void
gsk_http_server_post_stream_detach (GskHttpServerPostStream *post_stream,
				    gboolean                 is_server_dying);

/* amount of posted data to accumulate in the POST-data stream.
 */
#define MAX_POST_BUFFER		8192

typedef enum
{
  INIT,
  READING_REQUEST_FIRST_LINE,
  READING_REQUEST,
  READING_POST,
  DONE_READING
} ResponseParseState;

struct _GskHttpServerResponse
{
  GskHttpServer *server;
  GHashTable *request_parser_table;

  GskHttpRequest *request;
  GskHttpServerPostStream *post_data;

  ResponseParseState parse_state;

  /* all outgoing data goes here first temporarily */
  GskBuffer      outgoing;
  guint content_received;

  /* members from gsk_http_server_respond */
  GskHttpResponse *response;
  GskStream *content;

  /* have we written all the content out */
  guint is_done_writing : 1;

  /* have we gotten eof from 'content' */
  /* XXX: this flag should be removed: it is never meaningfully used.
          Instead content is set to NULL. */
  guint got_content_eof : 1;

  /* whether the user has queried this response yet */
  guint user_fetched : 1;

  /* whether this response failed for some reason */
  guint failed : 1;
  
  /* number of bytes of content written thus far */
  guint content_written;

  GskHttpServerResponse *next;
};
GSK_DECLARE_POOL_ALLOCATORS(GskHttpServerResponse, gsk_http_server_response, 6)

struct _GskHttpServerPostStreamClass 
{
  GskStreamClass stream_class;
};

struct _GskHttpServerPostStream 
{
  GskStream      stream;
  GskBuffer      buffer;
  GskHttpServer *server;
  guint          blocking_server_write : 1;

  /* for Content-length: handling */
  guint          has_length : 1;

  /* for Transfer-Encoding: chunked */
  guint          is_chunked : 1;
  guint          is_in_chunk_header : 1;

  guint          ended : 1;

  /* for Content-Length/Chunking */
  guint          cur_size;
};

static inline gboolean
gsk_http_server_response_is_done (GskHttpServerResponse *response)
{
  if (response->failed)
    return TRUE;
  return response->parse_state == DONE_READING
      && response->is_done_writing
      && response->outgoing.size == 0
      && response->content == NULL;
}

static inline void
gsk_http_server_response_destroy (GskHttpServerResponse *response,
				  gboolean               is_server_dying)
{
  if (response->request)
    g_object_unref (response->request);
  if (response->post_data)
    {
      gsk_http_server_post_stream_detach (response->post_data, is_server_dying);
      g_object_unref (response->post_data);
    }
  gsk_buffer_destruct (&response->outgoing);

  if (response->response)
    g_object_unref (response->response);
  if (response->content)
    g_object_unref (response->content);
  gsk_http_server_response_free (response);
}

static void gsk_http_server_prune_done_responses (GskHttpServer *server,
                                                  gboolean       may_read_shutdown);
static inline void
gsk_http_server_response_fail (GskHttpServerResponse *response,
			       const char            *explanation)
{
  /* NOTE: someday we're probably going to want a callback here. */ 
  GError *error = response->request
                ? GSK_HTTP_HEADER (response->request)->g_error
                : NULL;
  if (error == NULL)
    error = response->response
          ? GSK_HTTP_HEADER (response->response)->g_error
          : NULL;

#if 0
  if (error)
    g_debug ("gsk_http_server_response_fail: %s: %s", explanation, error->message);
  else
    g_debug ("gsk_http_server_response_fail: %s", explanation);
#endif

  response->failed = 1;
}

static gboolean
handle_content_is_readable (GskStream *content_stream, gpointer data)
{
  GskHttpServer *server = GSK_HTTP_SERVER (data);
  GskHttpServerResponse *trapped_response = server->trapped_response;
  GError *error = NULL;
  gboolean was_empty;
  g_return_val_if_fail (trapped_response != NULL && trapped_response->content == content_stream, FALSE);
  was_empty = trapped_response->outgoing.size == 0;
  if (GSK_HTTP_HEADER (trapped_response->response)->transfer_encoding_type == GSK_HTTP_TRANSFER_ENCODING_CHUNKED)
    {
      /* TODO: temporary buffer pooling?? (so that large reads can be spared a copy...) */
      char buf[4096];
      char len_prefix[64];
      gsize n_read = gsk_stream_read (content_stream, buf, sizeof (buf), &error);
      if (error != NULL)
	goto handle_error;
      if (n_read > 0)
        {
          g_snprintf (len_prefix, sizeof (len_prefix), "%x\r\n", (guint) n_read);
          gsk_buffer_append_string (&trapped_response->outgoing, len_prefix);
          gsk_buffer_append (&trapped_response->outgoing, buf, n_read);
          gsk_buffer_append (&trapped_response->outgoing, "\r\n", 2);
          trapped_response->content_received += n_read;
        }
    }
  else
    {
      trapped_response->content_received += gsk_stream_read_buffer (content_stream, &trapped_response->outgoing, &error);
    }
  if (error != NULL)
    goto handle_error;
  if (was_empty && trapped_response->outgoing.size > 0)
    gsk_io_notify_ready_to_read (server);
  if (trapped_response->outgoing.size > 0)
    gsk_io_mark_idle_notify_read (server);
  return TRUE;

handle_error:
  gsk_io_set_gerror (GSK_IO (server), GSK_IO_ERROR_READ, error);
  server->trapped_response = NULL;
  return FALSE;
}

static gboolean
should_close_after_this_response (GskHttpServerResponse *response)
{
  GskHttpHeader *resp = GSK_HTTP_HEADER (response->response);
  return gsk_http_header_get_connection (resp) == GSK_HTTP_CONNECTION_CLOSE;
}

static gboolean
handle_content_shutdown (GskStream *content_stream, gpointer data)
{
  GskHttpServer *server = GSK_HTTP_SERVER (data);
  GskHttpServerResponse *trapped_response = server->trapped_response;
  gint content_length;
  g_return_val_if_fail (trapped_response != NULL && trapped_response->content == content_stream, FALSE);
  trapped_response->content = NULL;
  server->trapped_response = NULL;
  if (GSK_HTTP_HEADER (trapped_response->response)->transfer_encoding_type == GSK_HTTP_TRANSFER_ENCODING_CHUNKED)
    {
      gboolean was_empty = trapped_response->outgoing.size == 0;
      gsk_buffer_append_string (&trapped_response->outgoing, "0\n");
      if (was_empty)
	gsk_io_mark_idle_notify_read (server);
    }
  content_length = GSK_HTTP_HEADER (trapped_response->response)->content_length;
  if (content_length >= 0)
    {
      if (trapped_response->content_received != (guint)content_length)
        {
          gsk_io_set_error (GSK_IO (server), GSK_IO_ERROR_READ,
                            GSK_ERROR_INVALID_STATE,
                            "expected %u bytes of data, got %u",
                            content_length,
                            trapped_response->content_received);
          g_object_unref (content_stream);
          return FALSE;
        }
    }
  if (trapped_response->outgoing.size == 0)
    {
      trapped_response->is_done_writing = 1;
      if (should_close_after_this_response (trapped_response))
        {
          gsk_io_notify_read_shutdown (server);
          if (gsk_io_get_is_writable (server))
            gsk_io_write_shutdown (server, NULL);
        }
    }
  gsk_http_server_prune_done_responses (server, TRUE);
  g_object_unref (content_stream);
  return FALSE;
}

static void
gsk_http_server_prune_done_responses (GskHttpServer *server,
                                      gboolean       may_read_shutdown)
{
  GskHttpServerResponse **pthis = &server->first_response;
  GskHttpServerResponse *last = NULL;
  GskHttpServerResponse *at;
  while (*pthis != NULL)
    {
      GskHttpServerResponse *at = *pthis;
      if (gsk_http_server_response_is_done (at))
	{
          if (server->trapped_response == at)
	    {
	      if (at->content != NULL)
		gsk_stream_untrap_readable (at->content);
	      server->trapped_response = NULL;
	    }
	  *pthis = at->next;
	  gsk_http_server_response_destroy (at, FALSE);
	}
      else
	{
	  pthis = &at->next;
	  last = at;
	}
    }
  server->last_response = last;

  for (at = server->first_response; at != NULL; at = at->next)
    {
      if (!at->is_done_writing)
	break;
    }
  
  if ((at == NULL || at->is_done_writing)
   && (server->got_close || !gsk_io_get_is_writable (server)))
    {
      /* The server is no longer readable.
	 This automatically clears the idle-notify flag. */
      if (may_read_shutdown)
        gsk_io_notify_read_shutdown (server);
      else
        gsk_io_set_idle_notify_read (server, TRUE);
      return;
    }

  gsk_io_set_idle_notify_read (server, at != NULL && at->outgoing.size != 0);

  /* if we are blocking on the content-stream for data,
     trap its readability. */
  if (at != NULL && at->outgoing.size == 0 && at->content != NULL
   && server->read_poll && server->trapped_response != at)
    {
      /* Untrap the old stream (if applicable),
         and trap the correct one. */
      if (server->trapped_response != NULL
       && server->trapped_response->content != NULL)
        gsk_stream_untrap_readable (at->content);
      server->trapped_response = at;
      gsk_stream_trap_readable (at->content, handle_content_is_readable,
                                handle_content_shutdown,
                                g_object_ref (server), g_object_unref);
    }

}

/* --- i/o implementation --- */
static void
gsk_http_server_set_poll_read (GskIO *io, gboolean should_poll)
{
  GSK_HTTP_SERVER (io)->read_poll = should_poll;
}

static gboolean
gsk_http_server_shutdown_read   (GskIO      *io,
				 GError    **error)
{
  GskHttpServer *server = GSK_HTTP_SERVER (io);
  GskHttpServerResponse *at;
  guint n_to_shutdown = 0;
  GskStream **to_shutdown;
  guint i;
  for (at = server->first_response; at != NULL; at = at->next)
    if (!at->is_done_writing)
      {
        gsk_http_server_response_fail (at, "shutdown-read while data is still queued");
        if (at->content != NULL
         && gsk_io_get_is_readable (at->content))
          n_to_shutdown++;
      }
  to_shutdown = g_newa (GskStream *, n_to_shutdown);
  i = 0;
  for (at = server->first_response; at != NULL; at = at->next)
    if (!at->is_done_writing
     && at->content != NULL
     && gsk_io_get_is_readable (at->content))
      to_shutdown[i++] = g_object_ref (at->content);
  g_assert (i == n_to_shutdown);
  for (i = 0; i < n_to_shutdown; i++)
    {
      gsk_io_read_shutdown (to_shutdown[i], NULL);
      g_object_unref (to_shutdown[i]);
    }
  return TRUE;
}

static void
gsk_http_server_set_poll_write (GskIO *io, gboolean should_poll)
{
  GSK_HTTP_SERVER (io)->write_poll = should_poll;
}

static gboolean
gsk_http_server_shutdown_write  (GskIO      *io,
				 GError    **error)
{
  GskHttpServer *server = GSK_HTTP_SERVER (io);
  GskHttpServerResponse *at;
  for (at = server->first_response; at != NULL; at = at->next)
    {
      if (at->parse_state == READING_POST)
        {
          at->parse_state = DONE_READING;
          at->post_data->ended = 1;
          if (at->post_data->buffer.size == 0)
	    gsk_io_notify_read_shutdown (at->post_data);
        }
      else if (at->parse_state != DONE_READING)
        gsk_http_server_response_fail (at, "shutdown when not in done-reading state");
    }

  gsk_http_server_prune_done_responses (server, TRUE);

  gsk_io_read_shutdown (GSK_IO (server), NULL);

  gsk_hook_notify_shutdown (GSK_HTTP_SERVER_HOOK (server));
  return TRUE;
}

static guint
gsk_http_server_raw_read      (GskStream     *stream,
			       gpointer       data,
			       guint          length,
			       GError       **error)
{
  GskHttpServer *server = GSK_HTTP_SERVER (stream);
  GskHttpServerResponse *at;
  guint rv;
  for (at = server->first_response; at != NULL; at = at->next)
    {
      if (!at->is_done_writing)
	break;
      if (at->response == NULL)
	{
	  gsk_io_clear_idle_notify_read (server);
	  return 0;
	}
    }
  if (at == NULL)
    {
      gsk_io_clear_idle_notify_read (server);
      if (server->got_close || !gsk_io_get_is_writable (server))
        gsk_io_notify_read_shutdown (server);
      return 0;
    }

  /* ok, 'at' is a response that may have data which can be read out */
  rv = 0;
  while (at != NULL && at->response != NULL)
    {
      if (at->outgoing.size > 0)
	{
	  guint amt  = MIN (length - rv, at->outgoing.size);
	  if (amt)
	    {
	      gsk_buffer_read (&at->outgoing, (char *) data + rv, length - rv);
	      rv += amt;
	    }
	  if (rv == length)
	    break;
	}
      if (at->outgoing.size == 0 && at->content == NULL)
	{
	  /* ok, done with this response */
	  at->is_done_writing = TRUE;
	  if (gsk_http_header_get_connection (GSK_HTTP_HEADER (at->response)) == GSK_HTTP_CONNECTION_CLOSE)
	    {
	      server->got_close = TRUE;
	      break;
	    }
	  at = at->next;
	}
      else
	{
          break;
	}
    }

  gsk_http_server_prune_done_responses (server, rv == 0);

  return rv;
}

static GskHttpServerResponse *
create_new_response (GskHttpServer *server)
{
  GskHttpServerResponse *response = gsk_http_server_response_alloc ();
  response->server = server;
  response->request_parser_table = NULL;
  response->request = NULL;
  response->post_data = NULL;
  response->parse_state = INIT;
  gsk_buffer_construct (&response->outgoing);
  response->content_received = 0;
  response->response = NULL;
  response->content = NULL;
  response->is_done_writing = 0;
  response->got_content_eof = 0;
  response->user_fetched = 0;
  response->content_written = 0;
  response->failed = 0;
  response->next = NULL;

  /* append this response to the queue */
  if (server->last_response)
    server->last_response->next = response;
  else
    server->first_response = response;
  server->last_response = response;

  return response;
}

static void
first_line_parser_callback  (GskHttpServerResponse *response,
			     const char       *text)
{
  GError *error = NULL;
  g_assert (response->request == NULL);
  response->request = gsk_http_request_new_blank ();

  switch (gsk_http_request_parse_first_line (response->request, text, &error))
    {
    case GSK_HTTP_REQUEST_FIRST_LINE_ERROR:
      {
        GskHttpRequest *request = response->request;
        response->request = NULL;
        gsk_io_set_gerror (GSK_IO (response->server), GSK_IO_ERROR_WRITE, error);
        if (request != NULL)
          g_object_unref (request);
      }
      return;

    case GSK_HTTP_REQUEST_FIRST_LINE_SIMPLE:
      response->parse_state = DONE_READING;
      response->request_parser_table = gsk_http_header_get_parser_table (TRUE);
      response->post_data = NULL;
      gsk_hook_notify (GSK_HTTP_SERVER_HOOK (response->server));
      break;

    case GSK_HTTP_REQUEST_FIRST_LINE_FULL:
      response->parse_state = READING_REQUEST;
      response->request_parser_table = gsk_http_header_get_parser_table (TRUE);
      break;

    default:
      g_assert_not_reached ();
    }
}

static void
header_line_parser_callback (GskHttpServerResponse *response,
			     const char            *line)
{
  GskHttpHeaderLineParser *parser;
  char *lowercase;
  const char *colon;
  unsigned i;
  const char *val;
  if (line[0] == 0)
    {
      GskHttpVerb verb = response->request->verb;
      if (verb == GSK_HTTP_VERB_PUT
       || verb == GSK_HTTP_VERB_POST)
	{
	  GskHttpHeader *hdr = GSK_HTTP_HEADER (response->request);
	  gboolean chunked = hdr->transfer_encoding_type == GSK_HTTP_TRANSFER_ENCODING_CHUNKED;
	  gint content_length = hdr->content_length;
	  response->post_data = gsk_http_server_post_stream_new (response->server,
							         chunked,
							         content_length);
	  response->parse_state = READING_POST;
	}
      else
	{
	  response->parse_state = DONE_READING;
	  response->post_data = NULL;
	}
      gsk_hook_notify (GSK_HTTP_SERVER_HOOK (response->server));
      return;
    }

  colon = strchr (line, ':');
  if (colon == NULL)
    {
      g_warning ("no colon in header line");
      return;	/* XXX: error handling! */
    }

  /* lowercase the header */
  lowercase = g_alloca (colon - (char*)line + 1);
  for (i = 0; line[i] != ':'; i++)
    lowercase[i] = g_ascii_tolower (line[i]);
  lowercase[i] = '\0';
  
  parser = g_hash_table_lookup (response->request_parser_table, lowercase);
  if (parser == NULL)
    {
      /* XXX: error handling */
      gboolean is_nonstandard = (line[0] == 'x' || line[0] == 'X')
                              && line[1] == '-';
      if (!is_nonstandard)
        g_warning ("couldn't handle header line %s", line);
      return;
    }

  val = colon + 1;
  GSK_SKIP_WHITESPACE (val);
  if (! ((*parser->func) (GSK_HTTP_HEADER (response->request), val, parser->data)))
    {
      /* XXX: error handling */
      g_warning ("error parsing header line %s", line);
      return;
    }
}

#define MAX_STACK_ALLOC	4096

static guint
gsk_http_server_raw_write     (GskStream     *stream,
			       gconstpointer  data,
			       guint          length,
			       GError       **error)
{
  GskHttpServer *server = GSK_HTTP_SERVER (stream);
  GskHttpServerResponse *at;
  char stack_buf[MAX_STACK_ALLOC];


  /* TODO: need a zero-copy strategy */
  gsk_buffer_append (&server->incoming, data, length);

  while (server->incoming.size > 0)
    {
      if (server->last_response != NULL
       && server->last_response->parse_state != DONE_READING)
        at = server->last_response;
      else
        at = create_new_response (server);

      switch (at->parse_state)
        {
        case INIT:
          at->parse_state = READING_REQUEST_FIRST_LINE;
          break;
        case READING_REQUEST_FIRST_LINE:
          {
            int nl = gsk_buffer_index_of (&server->incoming, '\n');
            char *first_line;
            char *free_line = NULL;
            if (nl < 0)
              goto done;
            if (nl > MAX_STACK_ALLOC - 1)
              free_line = first_line = g_malloc (nl + 1);
            else
              first_line = stack_buf;
            gsk_buffer_read (&server->incoming, first_line, nl + 1);
            first_line[nl] = '\0';
            g_strchomp (first_line);
            first_line_parser_callback (at, first_line);
            g_free (free_line);
          }
          break;

        case READING_REQUEST:
          {
            int nl = gsk_buffer_index_of (&server->incoming, '\n');
            char *header_line;
            char *free_line = NULL;
            if (nl < 0)
              goto done;
            if (nl > MAX_STACK_ALLOC - 1)
              free_line = header_line = g_malloc (nl + 1);
            else
              header_line = stack_buf;
            gsk_buffer_read (&server->incoming, header_line, nl + 1);
            header_line[nl] = '\0';
            g_strchomp (header_line);
            header_line_parser_callback (at, header_line);
            g_free (free_line);
          }
          break;
        case READING_POST:
          if (gsk_http_server_post_stream_process (at->post_data))
            at->parse_state = DONE_READING;
          break;
        default:
          goto done;
        }
    }

done:
  return length;
}

static void
gsk_http_server_finalize (GObject *object)
{
  GskHttpServer *server = GSK_HTTP_SERVER (object);
  while (server->first_response)
    {
      GskHttpServerResponse *response = server->first_response;
      server->first_response = response->next;
      gsk_http_server_response_destroy (response, TRUE);
    }
  gsk_buffer_destruct (&server->incoming);
  gsk_hook_destruct (&server->has_request_hook);
  parent_class->finalize (object);
}

/* --- functions --- */
static void
gsk_http_server_init (GskHttpServer *http_server)
{
  GSK_HOOK_INIT (http_server, GskHttpServer, has_request_hook, 0, 
		 set_poll_request, shutdown_request);
  GSK_HOOK_MARK_FLAG (&http_server->has_request_hook, IS_AVAILABLE);
  gsk_io_mark_is_readable (http_server);
  gsk_io_mark_is_writable (http_server);
}

static void
gsk_http_server_class_init (GskHttpServerClass *class)
{
  GObjectClass *object_class = G_OBJECT_CLASS (class);
  GskStreamClass *stream_class = GSK_STREAM_CLASS (class);
  GskIOClass *io_class = GSK_IO_CLASS (class);
  parent_class = g_type_class_peek_parent (class);
  stream_class->raw_read = gsk_http_server_raw_read;
  stream_class->raw_write = gsk_http_server_raw_write;
  object_class->finalize = gsk_http_server_finalize;
  io_class->set_poll_read = gsk_http_server_set_poll_read;
  io_class->shutdown_read = gsk_http_server_shutdown_read;
  io_class->set_poll_write = gsk_http_server_set_poll_write;
  io_class->shutdown_write = gsk_http_server_shutdown_write;
  GSK_HOOK_CLASS_INIT (object_class, "request", GskHttpServer, has_request_hook);
}

GType gsk_http_server_get_type()
{
  static GType http_server_type = 0;
  if (!http_server_type)
    {
      static const GTypeInfo http_server_info =
      {
	sizeof(GskHttpServerClass),
	(GBaseInitFunc) NULL,
	(GBaseFinalizeFunc) NULL,
	(GClassInitFunc) gsk_http_server_class_init,
	NULL,		/* class_finalize */
	NULL,		/* class_data */
	sizeof (GskHttpServer),
	0,		/* n_preallocs */
	(GInstanceInitFunc) gsk_http_server_init,
	NULL		/* value_table */
      };
      http_server_type = g_type_register_static (GSK_TYPE_STREAM,
                                                 "GskHttpServer",
						 &http_server_info, 0);
    }
  return http_server_type;
}

/* === Implementation of Post/Put data streams === */
typedef struct _GskHttpServerPostStreamClass GskHttpServerPostStreamClass;
GType gsk_http_server_post_stream_get_type(void) G_GNUC_CONST;
#define GSK_TYPE_HTTP_SERVER_POST_STREAM			(gsk_http_server_post_stream_get_type ())
#define GSK_HTTP_SERVER_POST_STREAM(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSK_TYPE_HTTP_SERVER_POST_STREAM, GskHttpServerPostStream))
#define GSK_HTTP_SERVER_POST_STREAM_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GSK_TYPE_HTTP_SERVER_POST_STREAM, GskHttpServerPostStreamClass))
#define GSK_HTTP_SERVER_POST_STREAM_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GSK_TYPE_HTTP_SERVER_POST_STREAM, GskHttpServerPostStreamClass))
#define GSK_IS_HTTP_SERVER_POST_STREAM(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSK_TYPE_HTTP_SERVER_POST_STREAM))
#define GSK_IS_HTTP_SERVER_POST_STREAM_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), GSK_TYPE_HTTP_SERVER_POST_STREAM))

static GObjectClass *post_stream_parent_class = NULL;

static inline void
check_unblock_server (GskHttpServerPostStream *post_stream)
{
  if (post_stream->server
   && post_stream->blocking_server_write
   && post_stream->buffer.size < MAX_POST_BUFFER)
    {
      post_stream->blocking_server_write = FALSE;
      gsk_io_unblock_write (post_stream->server);
    }
  if (post_stream->buffer.size == 0)
    {
      if (post_stream->ended)
	gsk_io_notify_read_shutdown (post_stream);
      else
	gsk_io_clear_idle_notify_read (post_stream);
    }
}

static guint
gsk_http_server_post_stream_raw_read        (GskStream     *stream,
					     gpointer       data,
					     guint          length,
					     GError       **error)
{
  GskHttpServerPostStream *post_stream = GSK_HTTP_SERVER_POST_STREAM (stream);
  guint rv = MIN (length, post_stream->buffer.size);
  gsk_buffer_read (&post_stream->buffer, data, rv);
  check_unblock_server (post_stream);
  return rv;
}

static guint
gsk_http_server_post_stream_raw_read_buffer (GskStream     *stream,
					     GskBuffer     *buffer,
					     GError       **error)
{
  GskHttpServerPostStream *post_stream = GSK_HTTP_SERVER_POST_STREAM (stream);
  guint rv = gsk_buffer_drain (buffer, &post_stream->buffer);
  check_unblock_server (post_stream);
  return rv;
}

static void
gsk_http_server_post_stream_finalize (GObject *object)
{
  GskHttpServerPostStream *post_stream = GSK_HTTP_SERVER_POST_STREAM (object);
  gsk_buffer_destruct (&post_stream->buffer);
  post_stream_parent_class->finalize (object);
}

static void
gsk_http_server_post_stream_init (GskHttpServerPostStream *server_post_stream)
{
  gsk_stream_mark_is_readable (server_post_stream);
}

static void
gsk_http_server_post_stream_class_init (GskHttpServerPostStreamClass *class)
{
  GObjectClass *object_class = G_OBJECT_CLASS (class);
  GskStreamClass *stream_class = GSK_STREAM_CLASS (class);
  post_stream_parent_class = g_type_class_peek_parent (class);
  stream_class->raw_read = gsk_http_server_post_stream_raw_read;
  stream_class->raw_read_buffer = gsk_http_server_post_stream_raw_read_buffer;
  object_class->finalize = gsk_http_server_post_stream_finalize;
}

GType gsk_http_server_post_stream_get_type()
{
  static GType http_server_post_stream_type = 0;
  if (!http_server_post_stream_type)
    {
      static const GTypeInfo http_server_post_stream_info =
      {
	sizeof(GskHttpServerPostStreamClass),
	(GBaseInitFunc) NULL,
	(GBaseFinalizeFunc) NULL,
	(GClassInitFunc) gsk_http_server_post_stream_class_init,
	NULL,		/* class_finalize */
	NULL,		/* class_data */
	sizeof (GskHttpServerPostStream),
	0,		/* n_preallocs */
	(GInstanceInitFunc) gsk_http_server_post_stream_init,
	NULL		/* value_table */
      };
      http_server_post_stream_type = g_type_register_static (GSK_TYPE_STREAM,
                                                  "GskHttpServerPostStream",
						  &http_server_post_stream_info, 0);
    }
  return http_server_post_stream_type;
}

static GskHttpServerPostStream *
gsk_http_server_post_stream_new (GskHttpServer         *server,
				 gboolean               is_chunked,
				 gint                   length)
{
  GskHttpServerPostStream *rv;
  rv = g_object_new (GSK_TYPE_HTTP_SERVER_POST_STREAM, NULL);
  rv->server = server;
  if (is_chunked)
    { 
      rv->is_chunked = 1;
      rv->is_in_chunk_header = 1;
    }
  else if (length >= 0)
    {
      rv->has_length = 1;
      rv->cur_size = length;
    }
  else
    {
      rv->has_length = 0;
    }
  return rv;
}


/* --- implement post data processing --- */

/* Process data for Transfer-Encoding: chunked.
   Returns whether the stream has ended. */
static inline gboolean
process_chunked (GskHttpServerPostStream *post_stream)
{
  GskBuffer *orig = &post_stream->server->incoming;
  for (;;)
    {
      if (post_stream->is_in_chunk_header)
	{
	  int c = gsk_buffer_read_char (orig);
	  if (c == '\n')
	    {
	      post_stream->is_in_chunk_header = 0;
	      if (post_stream->cur_size == 0)
		return TRUE;
	    }
	  else if ('0' <= c && c <= '9')
	    {
	      post_stream->cur_size <<= 4;
	      post_stream->cur_size += (c - '0');
	      continue;
	    }
	  else if ('a' <= c && c <= 'f')
	    {
	      post_stream->cur_size <<= 4;
	      post_stream->cur_size += (c - 'a' + 10);
	      continue;
	    }
	  else if ('A' <= c && c <= 'F')
	    {
	      post_stream->cur_size <<= 4;
	      post_stream->cur_size += (c - 'A' + 10);
	      continue;
	    }
	  else if (c == -1)
	    break;
	  else
	    continue;
	}
      g_assert (!post_stream->is_in_chunk_header);
      {
	guint xfer = MIN (orig->size, post_stream->cur_size);
	gsk_buffer_transfer (&post_stream->buffer, orig, xfer);
	post_stream->cur_size -= xfer;
	if (post_stream->cur_size == 0)
	  post_stream->is_in_chunk_header = 1;
	else
	  break;
      }
    }
  return FALSE;
}


/* Process data for Transfer-Encoding: identity.
   Returns whether the stream has ended. */
static inline gboolean
process_unencoded (GskHttpServerPostStream *post_stream)
{
  GskBuffer *orig = &post_stream->server->incoming;
  if (post_stream->has_length)
    {
      guint xfer = MIN (orig->size, post_stream->cur_size);
      gsk_buffer_transfer (&post_stream->buffer, orig, xfer);
      post_stream->cur_size -= xfer;
      if (post_stream->cur_size == 0)
	return TRUE;
    }
  else
    gsk_buffer_drain (&post_stream->buffer, orig);

  return FALSE;
}

/* Returns whether the post-data is over */
static gboolean
gsk_http_server_post_stream_process (GskHttpServerPostStream *post_stream)
{
  gboolean ended;
  if (post_stream->is_chunked)
    ended = process_chunked (post_stream);
  else
    ended = process_unencoded (post_stream);
  gsk_io_set_idle_notify_read (GSK_IO (post_stream),
			       post_stream->buffer.size > 0);
  if (post_stream->buffer.size > MAX_POST_BUFFER
   && !post_stream->blocking_server_write
   && post_stream->server != NULL               /* always true, i think */
   && !ended)
    {
      post_stream->blocking_server_write = 1;
      gsk_io_unblock_write (post_stream->server);
    }
  if (ended)
    {
      post_stream->ended = 1;
      if (post_stream->buffer.size == 0)
	gsk_io_notify_read_shutdown (post_stream);
    }
  return ended;
}

static void
gsk_http_server_post_stream_detach (GskHttpServerPostStream *post_stream,
				    gboolean                 is_server_dying)
{
  if (!is_server_dying && post_stream->blocking_server_write)
    {
      post_stream->blocking_server_write = 0;
      gsk_io_unblock_write (post_stream->server);
    }
  post_stream->server = NULL;
  post_stream->ended = 1;
  if (post_stream->buffer.size == 0)
    gsk_io_notify_read_shutdown (post_stream);
}

/* --- public interface --- */
/**
 * gsk_http_server_new:
 *
 * Allocate a new HTTP server protocol processor.
 * (Note that generally you will need to connect it
 * to an accepted socket)
 *
 * returns: the newly allocated server.
 */
GskHttpServer *
gsk_http_server_new (void)
{
  return g_object_new (GSK_TYPE_HTTP_SERVER, NULL);
}

/**
 * gsk_http_server_get_request:
 * @server: the HTTP server to grab the request from.
 * @request_out: location to store a reference to the HTTP request.
 * @post_data_out: location to store a reference to the HTTP request's POST data,
 * or NULL will be stored if this is not a POST-type request.
 *
 * Grab a client request if available.  Use gsk_http_server_trap()
 * to get notification when a request is available.
 *
 * The corresponding POST data stream must be retrieved at the 
 * same time.
 *
 * returns: whether a request was successfully dequeued.
 */
gboolean
gsk_http_server_get_request (GskHttpServer   *server,
			     GskHttpRequest **request_out,
			     GskStream      **post_data_out)
{
  GskHttpServerResponse *sresponse;
  for (sresponse = server->first_response;
       sresponse != NULL;
       sresponse = sresponse->next)
    {
      if (!sresponse->user_fetched)
	{
	  GskHttpRequest *request = sresponse->request;
	  GskHttpServerPostStream *post_data = sresponse->post_data;
	  *request_out = g_object_ref (request);
	  if (post_data_out)
	    {
	      if (post_data)
		*post_data_out = g_object_ref (post_data);
	      else
		*post_data_out = NULL;
	    }
	  sresponse->user_fetched = 1;
	  return TRUE;
	}
    }
  return FALSE;
}

/**
 * gsk_http_server_respond:
 * @server: the server to write the response to.
 * @request: the request obtained with gsk_http_server_get_request().
 * @response: the response constructed to this request.
 * @content: content data if appropriate to this request.
 *
 * Give a response to a client's request.
 */
void
gsk_http_server_respond     (GskHttpServer   *server,
			     GskHttpRequest  *request,
			     GskHttpResponse *response,
			     GskStream       *content)
{
  GskHttpServerResponse *sresponse;
  g_return_if_fail (content == NULL || !gsk_hook_is_trapped (GSK_IO_READ_HOOK (content)));
  g_return_if_fail (response != NULL);
  for (sresponse = server->first_response;
       sresponse != NULL;
       sresponse = sresponse->next)
    {
      if (sresponse->request == request)
	break;
    }
  g_return_if_fail (sresponse != NULL);
  if (sresponse->response != NULL)
    {
      g_warning ("got multiple responses to request for '%s'", request->path);
      return;
    }
  g_return_if_fail (sresponse->content == NULL);

  if (content != NULL && !GSK_HTTP_HEADER (response)->has_content_type)
    g_warning ("HTTP response has content but no Content-Type header");

  sresponse->response = g_object_ref (response);
  if (content)
    sresponse->content = g_object_ref (content);
  gsk_http_header_to_buffer (GSK_HTTP_HEADER (response), &sresponse->outgoing);
  
  if (!gsk_io_get_idle_notify_read (server))
    {
      for (sresponse = server->first_response;
	   sresponse != NULL;
	   sresponse = sresponse->next)
	{
	  if (!sresponse->is_done_writing)
	    {
	      if (!sresponse->request)
		return;
	      if (sresponse->outgoing.size == 0
		  && sresponse->content != NULL
		  && !sresponse->got_content_eof)
		return;
	      gsk_io_mark_idle_notify_read (server);
	      return;
	    }
	}
    }
}


syntax highlighted by Code2HTML, v. 0.9.1