#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