#include "gskhttpcontent.h"
#include "gskprefixtree.h"
#include "../url/gskurl.h"
#include "../gskmemory.h"
#include "../gskutils.h"
#include "../gskstreamfd.h"
#include "../gskstreamlistenersocket.h"
#include "../mime/gskmimemultipartdecoder.h"
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
/* --- helper functions --- */
static void
reverse_string (char *out, const char *in, guint len)
{
guint i;
in += len;
for (i = 0; i < len; i++)
*out++ = *(--in);
*out = 0;
}
/* --- a Handler: a ref-counted callback, data, destroy triple */
typedef enum
{
HANDLER_RAW,
HANDLER_CGI
} HandlerType;
struct _GskHttpContentHandler
{
guint ref_count;
HandlerType type;
gpointer data;
union {
GskHttpContentFunc raw;
GskHttpContentCGIFunc cgi;
} func;
GDestroyNotify destroy;
/* these form rings of handlers, once they
are added to a content. */
GskHttpContentHandler *next, *prev;
};
typedef GskHttpContentHandler Handler;
static inline Handler *
handler_alloc (HandlerType type,
GHookFunc func,
gpointer data,
GDestroyNotify destroy)
{
Handler *rv = g_new (Handler, 1);
rv->ref_count = 1;
rv->type = type;
rv->data = data;
rv->destroy = destroy;
rv->func.raw = (GskHttpContentFunc) func;
rv->next = rv->prev = NULL;
return rv;
}
void
gsk_http_content_handler_unref (Handler *handler)
{
if (--(handler->ref_count) == 0)
{
if (handler->destroy)
handler->destroy (handler->data);
g_free (handler);
}
}
void
gsk_http_content_handler_ref (Handler *handler)
{
g_return_if_fail (handler->ref_count > 0);
++(handler->ref_count);
}
static inline void handler_ref (Handler *handler)
{
++(handler->ref_count);
}
/*
* _ _ _ _
* __| |__ _| |_ __ _| |__ __ _ ___ ___ ___| |_ _ _ _ __ ___
* / _` / _` | _/ _` | '_ \/ _` (_-</ -_)___| _| || | '_ \/ -_)
* \__,_\__,_|\__\__,_|_.__/\__,_/__/\___| \__|\_, | .__/\___|
* |__/|_|
* _ __ __
* __| |_ _ _ / _|/ _|
* (_-< _| || | _| _|
* /__/\__|\_,_|_| |_|
*/
typedef struct _SuffixList SuffixList;
typedef struct _PathTable PathTable;
typedef struct _PathVHostTable PathVHostTable;
struct _SuffixList
{
GskPrefixTree *suffix_to_handler; /* reverse the string before invoking */
Handler *no_suffix_handler;
};
struct _PathTable
{
GHashTable *exact;
GskPrefixTree *prefix_to_suffix_list;
SuffixList *no_prefix_list; /* includes no path at all case */
};
struct _PathVHostTable
{
GHashTable *vhost_to_path_table;
PathTable *no_vhost_path_table;
};
struct _GskHttpContent
{
/* Table of contents */
GskPrefixTree *user_agent_to_path_vhost_table;
PathVHostTable *path_vhost_table;
/* Mime types by path */
/* final target of all these is a pair of strings concatenated
(including the NULs) */
GskPrefixTree *mime_suffix_to_prefix_tree_to_typepair;
GskPrefixTree *mime_suffix_to_typepair; /* no prefix */
GskPrefixTree *mime_prefix_to_typepair; /* no suffix */
char *mime_default_typepair;
GskHttpContentErrorHandler error_handler;
gpointer error_data;
GDestroyNotify error_destroy;
};
/* --- public interface --- */
/* construction */
static SuffixList *
suffix_list_new (void)
{
return g_new0 (SuffixList, 1);
}
static PathTable *
path_table_new (void)
{
PathTable *rv = g_new (PathTable, 1);
rv->exact = g_hash_table_new (g_str_hash, g_str_equal);
rv->prefix_to_suffix_list = NULL;
rv->no_prefix_list = suffix_list_new ();
return rv;
}
static PathVHostTable *
path_vhost_table_new (void)
{
PathVHostTable *rv = g_new (PathVHostTable, 1);
rv->vhost_to_path_table = g_hash_table_new (g_str_hash, g_str_equal);
rv->no_vhost_path_table = path_table_new ();
return rv;
}
static void
server_respond_printf (GskHttpServer *server,
GskHttpRequest *request,
GskHttpStatus status_code,
const char *format,
...)
{
va_list args;
GskHttpResponse *response;
GskStream *stream;
guint str_len;
char *str;
va_start (args,format);
str = g_strdup_vprintf (format, args);
va_end (args);
str_len = strlen (str);
stream = gsk_memory_slab_source_new (str, str_len, g_free, str);
response = gsk_http_response_from_request (request, status_code, str_len);
gsk_http_response_set_content_type (response, "text");
gsk_http_response_set_content_subtype (response, "html");
gsk_http_server_respond (server, request, response, stream);
g_object_unref (stream);
g_object_unref (response);
}
static void
default_error_handler (GskHttpContent *content,
GError *error,
GskHttpServer *server,
GskHttpRequest *request,
GskHttpStatus code,
gpointer data)
{
GEnumClass *class = g_type_class_ref (GSK_TYPE_HTTP_STATUS);
GEnumValue *value = g_enum_get_value (class, code);
server_respond_printf (server, request, code,
"<html>\n"
" <head><title>%u: %s</title></head>\n"
" <body><h1>%s</h1>\n"
" </body>\n"
"</html>\n",
code,
value ? value->value_nick : "unknown error",
value ? value->value_name : "Unknown Error");
g_type_class_unref (class);
}
/**
* gsk_http_content_new:
*
* Allocate a new, empty content set.
*
* returns: a newly allocated content database.
*/
GskHttpContent *gsk_http_content_new (void)
{
GskHttpContent *content = g_new0 (GskHttpContent, 1);
content->user_agent_to_path_vhost_table = NULL;
content->path_vhost_table = path_vhost_table_new ();
content->error_handler = default_error_handler;
return content;
}
/**
* gsk_http_content_handler_new:
* @func: function to call that will try and
* handle the HTTP request.
* @data: data to pass to function.
* @destroy: function to call when the handler is destroyed.
* (after all requests are done).
*
* Allocate a new content handler.
*
* This should be added to a GskHttpContent
* using gsk_http_content_add_handler()
* and then unrefed with gsk_http_content_handler_unref().
*
* returns: the newly allocated handler.
*/
GskHttpContentHandler *
gsk_http_content_handler_new (GskHttpContentFunc func,
gpointer data,
GDestroyNotify destroy)
{
return handler_alloc (HANDLER_RAW, (GHookFunc) func, data, destroy);
}
/**
* gsk_http_content_handler_new_cgi:
* @func: function to call that will try and
* handle the CGI request.
* @data: data to pass to function.
* @destroy: function to call when the handler is destroyed.
* (after all requests are done).
*
* Allocate a new content handler.
*
* This should be added to a GskHttpContent
* using gsk_http_content_add_handler()
* and then unrefed with gsk_http_content_handler_unref().
*
* returns: the newly allocated handler.
*/
GskHttpContentHandler *
gsk_http_content_handler_new_cgi (GskHttpContentCGIFunc func,
gpointer data,
GDestroyNotify destroy)
{
return handler_alloc (HANDLER_CGI, (GHookFunc) func, data, destroy);
}
static void
handler_ring_add (Handler **ring,
Handler *new,
GskHttpContentAction action)
{
g_assert (new->next == NULL && new->prev == NULL);
switch (action)
{
case GSK_HTTP_CONTENT_PREPEND:
if (*ring == NULL)
{
*ring = new;
new->next = new->prev = new;
}
else
{
new->next = *ring;
new->prev = (*ring)->prev;
new->next->prev = new;
new->prev->next = new;
}
break;
case GSK_HTTP_CONTENT_APPEND:
if (*ring == NULL)
{
*ring = new;
new->next = new->prev = new;
}
else
{
new->prev = *ring;
new->next = (*ring)->next;
new->next->prev = new;
new->prev->next = new;
}
break;
case GSK_HTTP_CONTENT_REPLACE:
if (*ring != NULL)
{
Handler *at = *ring;
do
{
Handler *next = at->next;
gsk_http_content_handler_unref (at);
at = next;
}
while (at != *ring);
}
*ring = new;
new->next = new->prev = new;
break;
default:
g_assert_not_reached ();
}
handler_ref (new);
}
static void
suffix_list_add (SuffixList *sl,
const GskHttpContentId *id,
Handler *handler,
GskHttpContentAction action)
{
if (id->path_suffix)
{
guint len = strlen (id->path_suffix);
char *rev = g_alloca (len + 1);
Handler *ring;
reverse_string (rev, id->path_suffix, len);
ring = gsk_prefix_tree_lookup_exact (sl->suffix_to_handler, rev);
handler_ring_add (&ring, handler, action);
gsk_prefix_tree_insert (&sl->suffix_to_handler, rev, ring);
}
else
{
handler_ring_add (&sl->no_suffix_handler, handler, action);
}
}
static void
path_table_add (PathTable *table,
const GskHttpContentId *id,
Handler *handler,
GskHttpContentAction action)
{
if (id->path)
{
char *table_path = NULL;
Handler *ring = NULL;
g_hash_table_lookup_extended (table->exact, id->path, (gpointer*) &table_path, (gpointer*) &ring);
handler_ring_add (&ring, handler, action);
if (table_path == NULL)
table_path = g_strdup (id->path);
g_hash_table_insert (table->exact, table_path, ring);
}
else if (id->path_prefix)
{
SuffixList *sl = gsk_prefix_tree_lookup_exact (table->prefix_to_suffix_list,
id->path_prefix);
if (sl == NULL)
{
sl = suffix_list_new ();
gsk_prefix_tree_insert (&table->prefix_to_suffix_list,
id->path_prefix, sl);
}
suffix_list_add (sl, id, handler, action);
}
else
{
suffix_list_add (table->no_prefix_list, id, handler, action);
}
}
static void
path_vhost_table_add (PathVHostTable *table,
const GskHttpContentId *id,
Handler *handler,
GskHttpContentAction action)
{
PathTable *pt;
if (id->host)
{
pt = g_hash_table_lookup (table->vhost_to_path_table, id->host);
if (pt == NULL)
{
pt = path_table_new ();
g_hash_table_insert (table->vhost_to_path_table,
g_strdup (id->host), pt);
}
}
else
{
pt = table->no_vhost_path_table;
}
g_assert (pt != NULL);
path_table_add (pt, id, handler, action);
}
/**
* gsk_http_content_add_handler:
* @content: database to add the content to.
* @id: location within the server to add the entry to.
* @handler: a handler to process the data.
* @action: how this handler relates to other handlers.
* It may be a pre-filter, or a post-filter, or replace
* the currently registered handlers.
*
* Add the handler to the content database.
* This increases the ref-count on the handler,
* so you must also unref it.
*
* You cannot add a handler to more than
* one database.
*/
void
gsk_http_content_add_handler (GskHttpContent *content,
const GskHttpContentId *id,
GskHttpContentHandler *handler,
GskHttpContentAction action)
{
if (id->user_agent_prefix)
{
PathVHostTable *pvh_table;
pvh_table = gsk_prefix_tree_lookup (content->user_agent_to_path_vhost_table,
id->user_agent_prefix);
if (pvh_table == NULL)
{
pvh_table = path_vhost_table_new ();
gsk_prefix_tree_insert (&content->user_agent_to_path_vhost_table,
id->user_agent_prefix,
pvh_table);
}
path_vhost_table_add (pvh_table, id, handler, action);
}
else
{
path_vhost_table_add (content->path_vhost_table, id, handler, action);
}
}
/* --- mime-type table --- */
/**
* gsk_http_content_set_mime_type:
* @content: content-database to register this mime information with.
* @prefix: path prefix that maps to this type/subtype pair.
* (may be NULL for an empty prefix test).
* @suffix: path suffix that maps to this type/subtype pair.
* (may be NULL for an empty suffix test).
* @type: main type associated with this portion of the URI space.
* @subtype: subtype associated with this portion of the URI space.
*
* Register information about which paths are associated with
* which mime types. This is used for data and file serving,
* or you can explicitly use it from a custom handler.
*/
void
gsk_http_content_set_mime_type (GskHttpContent *content,
const char *prefix,
const char *suffix,
const char *type,
const char *subtype)
{
char *rev_suffix = NULL;
char *typepair;
if (suffix)
{
guint len = strlen (suffix);
rev_suffix = g_alloca (len + 1);
reverse_string (rev_suffix, suffix, len);
}
{
guint len_type = strlen (type);
typepair = g_malloc (len_type + 1 + strlen (subtype) + 1);
strcpy (typepair, type);
strcpy (typepair + len_type + 1, subtype);
}
if (prefix && suffix)
{
GskPrefixTree *pref;
pref = gsk_prefix_tree_lookup_exact (content->mime_suffix_to_prefix_tree_to_typepair,
rev_suffix);
gsk_prefix_tree_insert (&pref, prefix, typepair);
gsk_prefix_tree_insert (&content->mime_suffix_to_prefix_tree_to_typepair,
rev_suffix, pref);
}
else if (prefix)
{
char *old = gsk_prefix_tree_insert (&content->mime_prefix_to_typepair,
prefix, typepair);
g_free (old);
}
else if (suffix)
{
char *old = gsk_prefix_tree_insert (&content->mime_suffix_to_typepair,
rev_suffix, typepair);
g_free (old);
}
else
{
g_free (content->mime_default_typepair);
content->mime_default_typepair = typepair;
}
}
/**
* gsk_http_content_set_default_mime_type:
* @content: content-database to register this mime information with.
* @type: default mime type.
* @subtype: default mime subtype.
*
* If no other more specific path is found,
* this will be the assigned type.
*
* You should always call this function if you plan
* on serving files or data.
*
* This is equivalent to calling gsk_http_content_set_mime_type()
* with prefix==suffix==NULL.
*/
void
gsk_http_content_set_default_mime_type (GskHttpContent *content,
const char *type,
const char *subtype)
{
gsk_http_content_set_mime_type (content, NULL, NULL, type, subtype);
}
gboolean
gsk_http_content_get_mime_type (GskHttpContent *content,
const char *path,
const char **type_out,
const char **subtype_out)
{
guint path_len = strlen (path);
char *rev_path = g_alloca (path_len + 1);
const char *typepair = NULL;
reverse_string (rev_path, path, path_len);
/* try prefix/suffix tree */
{
GskPrefixTree *pref_tree = gsk_prefix_tree_lookup (content->mime_suffix_to_prefix_tree_to_typepair,
rev_path);
typepair = gsk_prefix_tree_lookup (pref_tree, path);
if (typepair)
goto done;
}
/* try suffix tree */
typepair = gsk_prefix_tree_lookup (content->mime_suffix_to_typepair, rev_path);
if (typepair)
goto done;
/* try prefix tree */
typepair = gsk_prefix_tree_lookup (content->mime_prefix_to_typepair, path);
if (typepair)
goto done;
typepair = content->mime_default_typepair;
done:
if (typepair == NULL)
return FALSE;
*type_out = typepair;
*subtype_out = strchr (typepair, 0) + 1;
return TRUE;
}
/**
* gsk_http_content_set_error_handler:
* @content: content-database to affect.
* @handler: function to call to display error data.
* @data: data to pass to @handler.
* @destroy: function to call when handler is destroyed.
*
* This function is intended to respond to other handlers'
* error returns. It is also called in the command-not-found
* case.
*/
void
gsk_http_content_set_error_handler (GskHttpContent *content,
GskHttpContentErrorHandler handler,
gpointer data,
GDestroyNotify destroy)
{
if (content->error_destroy)
(*content->error_destroy) (content->error_data);
content->error_handler = handler;
content->error_data = data;
content->error_destroy = destroy;
}
/* --- Responding to a request: CGI implementation --- */
typedef struct _CGIRequestInfo CGIRequestInfo;
struct _CGIRequestInfo
{
Handler *handler;
GskHttpContent *content;
GskHttpServer *server;
GskHttpRequest *request;
GPtrArray *mime_pieces;
};
static CGIRequestInfo *
cgi_request_info_new (Handler *handler,
GskHttpContent *content,
GskHttpServer *server,
GskHttpRequest *request)
{
CGIRequestInfo *ri = g_new (CGIRequestInfo, 1);
ri->handler = handler;
ri->content = content;
ri->server = server;
ri->request = request;
ri->mime_pieces = g_ptr_array_new ();
handler_ref (ri->handler);
return ri;
}
static void
cgi_request_info_free (CGIRequestInfo *ri)
{
guint i;
gsk_http_content_handler_unref (ri->handler);
for (i = 0; i < ri->mime_pieces->len; i++)
gsk_mime_multipart_piece_unref (ri->mime_pieces->pdata[i]);
g_ptr_array_free (ri->mime_pieces, TRUE);
g_free (ri);
}
static void
cgi_request_info_callback (CGIRequestInfo *ri)
{
ri->handler->func.cgi (ri->content, ri->handler,
ri->server, ri->request,
ri->mime_pieces->len,
(GskMimeMultipartPiece **) (ri->mime_pieces->pdata),
ri->handler->data);
}
static void
append_kv_as_mime_piece_to_ptr_array (gpointer key, gpointer value, gpointer data)
{
const char *k = key;
const char *v = value;
GPtrArray *arr = data;
gpointer copy = g_strdup (v);
GskMimeMultipartPiece *piece = gsk_mime_multipart_piece_alloc ();
gsk_mime_multipart_piece_set_id (piece, k);
gsk_mime_multipart_piece_set_data (piece, copy, strlen (v), g_free, copy);
g_ptr_array_add (arr, piece);
}
static void
handle_cgi_with_urlencoded_string (Handler *handler,
GskHttpContent *content,
GskHttpServer *server,
GskHttpRequest *request,
const char *query_string)
{
GPtrArray *mime_pieces = g_ptr_array_new ();
guint i;
char **kv_pairs = gsk_url_split_form_urlencoded (query_string);
for (i = 0; kv_pairs[i]; i += 2)
append_kv_as_mime_piece_to_ptr_array (kv_pairs[i], kv_pairs[i+1],
mime_pieces);
g_strfreev (kv_pairs);
handler->func.cgi (content, handler,
server, request,
mime_pieces->len,
(GskMimeMultipartPiece **) (mime_pieces->pdata),
handler->data);
for (i = 0; i < mime_pieces->len; i++)
gsk_mime_multipart_piece_unref (mime_pieces->pdata[i]);
g_ptr_array_free (mime_pieces, TRUE);
}
static void
urlencoded_buffer_ready (GskBuffer *buffer,
gpointer data)
{
CGIRequestInfo *ri = data;
char *qstr = g_malloc (buffer->size + 1);
qstr[buffer->size] = 0;
gsk_buffer_read (buffer, qstr, buffer->size);
handle_cgi_with_urlencoded_string (ri->handler, ri->content, ri->server,
ri->request, qstr);
g_free (qstr);
}
static void
do_cgi_with_urlencoded_form_data (Handler *handler,
GskHttpContent *content,
GskHttpServer *server,
GskHttpRequest *request,
GskStream *post_data)
{
CGIRequestInfo *ri = cgi_request_info_new (handler, content, server, request);
GskStream *content_sink;
content_sink = gsk_memory_buffer_sink_new (urlencoded_buffer_ready,
ri,
(GDestroyNotify) cgi_request_info_free);
gsk_stream_attach (post_data, content_sink, NULL);
g_object_unref (content_sink);
}
static gboolean
handle_piece_ready (GskMimeMultipartDecoder *decoder,
gpointer data)
{
CGIRequestInfo *ri = data;
GskMimeMultipartPiece *piece = gsk_mime_multipart_decoder_get_piece (decoder);
if (piece)
g_ptr_array_add (ri->mime_pieces, piece);
return TRUE;
}
static gboolean
handle_decoder_shutdown (GskMimeMultipartDecoder *decoder,
gpointer data)
{
CGIRequestInfo *ri = data;
cgi_request_info_callback (ri);
return FALSE;
}
static void
do_cgi_with_multipart_form_data (Handler *handler,
GskHttpContent *content,
GskHttpServer *server,
GskHttpRequest *request,
GskStream *post_data)
{
GskMimeMultipartDecoder *decoder = gsk_mime_multipart_decoder_new (NULL);
CGIRequestInfo *ri = cgi_request_info_new (handler, content, server, request);
gsk_mime_multipart_decoder_set_mode (decoder, GSK_MIME_MULTIPART_DECODER_MODE_ALWAYS_MEMORY);
gsk_mime_multipart_decoder_trap (decoder, handle_piece_ready, handle_decoder_shutdown,
ri, (GDestroyNotify) cgi_request_info_free);
}
/* precondition: request->path must contain a '?' */
static void
do_cgi_with_get_form_data (Handler *handler,
GskHttpContent *content,
GskHttpServer *server,
GskHttpRequest *request)
{
handle_cgi_with_urlencoded_string (handler, content, server, request,
strchr (request->path, '?') + 1);
}
static GskHttpContentResult
try_cgi_handler (Handler *handler,
GskHttpContent *content,
GskHttpServer *server,
GskHttpRequest *request,
GskStream *post_data)
{
/* does this HTTP header look like a CGI? */
/* requirement are either an interesting query string
or the correct content type */
const char *type = GSK_HTTP_HEADER (request)->content_type;
const char *subtype = GSK_HTTP_HEADER (request)->content_subtype;
gboolean has_urlencodedform_data = request->verb ==GSK_HTTP_VERB_POST
&& type != NULL && subtype != NULL
&& strcmp (type, "application") == 0
&& strcmp (subtype, "x-www-form-urlencoded") == 0;
gboolean has_multipartform_data = request->verb ==GSK_HTTP_VERB_POST
&& type != NULL && subtype != NULL
&& strcmp (type, "multipart") == 0
&& strcmp (subtype, "form-data") == 0;
gboolean has_interesting_query = request->verb == GSK_HTTP_VERB_GET
&& strchr (request->path, '?') != NULL;
gboolean has_form_data = has_urlencodedform_data || has_multipartform_data;
if (!has_form_data && !has_interesting_query)
return GSK_HTTP_CONTENT_CHAIN;
if (has_urlencodedform_data)
do_cgi_with_urlencoded_form_data (handler, content, server, request, post_data);
else if (has_multipartform_data)
do_cgi_with_multipart_form_data (handler, content, server, request, post_data);
else
do_cgi_with_get_form_data (handler, content, server, request);
return GSK_HTTP_CONTENT_OK;
}
/* --- Responding to a request: Handler finding and invocation --- */
static GskHttpContentResult
one_handler_response (Handler *handler,
GskHttpContent *content,
GskHttpServer *server,
GskHttpRequest *request,
GskStream *post_data)
{
switch (handler->type)
{
case HANDLER_RAW:
return handler->func.raw (content, handler, server, request, post_data, handler->data);
case HANDLER_CGI:
return try_cgi_handler (handler, content, server, request, post_data);
default:
g_return_val_if_reached (GSK_HTTP_CONTENT_ERROR);
}
}
static GskHttpContentResult
handler_ring_respond (Handler *handlers,
GskHttpContent *content,
GskHttpServer *server,
GskHttpRequest *request,
GskStream *post_data)
{
Handler *h = handlers;
if (h == NULL)
return GSK_HTTP_CONTENT_CHAIN;
do
{
GskHttpContentResult rv = one_handler_response (h, content, server, request, post_data);
if (rv != GSK_HTTP_CONTENT_CHAIN)
return rv;
h = h->next;
}
while (h != handlers);
return GSK_HTTP_CONTENT_CHAIN;
}
static GskHttpContentResult
suffix_list_respond (SuffixList *suffix,
GskHttpContent *content,
GskHttpServer *server,
GskHttpRequest *request,
GskStream *post_data)
{
const char *end = strchr (request->path, '?');
char *rev;
GSList *list, *at;
if (end == NULL)
end = strchr (request->path, 0);
rev = g_alloca (end - request->path + 1);
reverse_string (rev, request->path, end - request->path);
list = gsk_prefix_tree_lookup_all (suffix->suffix_to_handler, rev);
for (at = list; at; at = at->next)
{
GskHttpContentResult rv;
rv = handler_ring_respond (at->data, content, server, request, post_data);
if (rv != GSK_HTTP_CONTENT_CHAIN)
{
g_slist_free (list);
return rv;
}
}
g_slist_free (list);
return handler_ring_respond (suffix->no_suffix_handler, content, server, request, post_data);
}
static GskHttpContentResult
path_table_respond (PathTable *table,
GskHttpContent *content,
GskHttpServer *server,
GskHttpRequest *request,
GskStream *post_data)
{
Handler *h = g_hash_table_lookup (table->exact, request->path);
GskHttpContentResult rv = handler_ring_respond (h, content, server, request, post_data);
GSList *list, *at;
if (rv != GSK_HTTP_CONTENT_CHAIN)
return rv;
list = gsk_prefix_tree_lookup_all (table->prefix_to_suffix_list, request->path);
if (list == NULL)
return GSK_HTTP_CONTENT_CHAIN;
for (at = list; at; at = at->next)
{
SuffixList *sl = at->data;
rv = suffix_list_respond (sl, content, server, request, post_data);
if (rv != GSK_HTTP_CONTENT_CHAIN)
{
g_slist_free (list);
return rv;
}
}
g_slist_free (list);
return GSK_HTTP_CONTENT_CHAIN;
}
static GskHttpContentResult
path_vhost_table_respond (PathVHostTable *table,
GskHttpContent *content,
GskHttpServer *server,
GskHttpRequest *request,
GskStream *post_data)
{
GskHttpContentResult rv;
if (request->host != NULL)
{
PathTable *tab = g_hash_table_lookup (table->vhost_to_path_table, request->host);
if (tab != NULL)
{
rv = path_table_respond (tab, content, server, request, post_data);
if (rv != GSK_HTTP_CONTENT_CHAIN)
return rv;
}
}
return path_table_respond (table->no_vhost_path_table, content, server, request, post_data);
}
/**
* gsk_http_content_respond:
* @content: the content database that will define the response.
* @server: the server which received the request.
* @request: the server's request.
* @post_data: the server's post data, or NULL if none.
*
* Respond to the given HTTP query,
* using the registered content in the GskHttpContent.
*/
void gsk_http_content_respond(GskHttpContent *content,
GskHttpServer *server,
GskHttpRequest *request,
GskStream *post_data)
{
Handler *rv = NULL;
GError *error;
if (request->user_agent != NULL)
{
GSList *list;
GSList *at;
list = gsk_prefix_tree_lookup_all (content->user_agent_to_path_vhost_table,
request->user_agent);
for (at = list; at; at = at->next)
{
PathVHostTable *pvh_table = at->data;
switch (path_vhost_table_respond (pvh_table, content, server, request, post_data))
{
case GSK_HTTP_CONTENT_OK:
g_slist_free (list);
return;
case GSK_HTTP_CONTENT_CHAIN:
break;
case GSK_HTTP_CONTENT_ERROR:
g_slist_free (list);
goto serve_error_page;
}
}
g_slist_free (list);
}
if (rv == NULL)
{
switch (path_vhost_table_respond (content->path_vhost_table,
content, server, request, post_data))
{
case GSK_HTTP_CONTENT_OK:
return;
case GSK_HTTP_CONTENT_CHAIN:
break;
case GSK_HTTP_CONTENT_ERROR:
goto serve_error_page;
}
}
/* nobody responded. */
error = g_error_new (GSK_G_ERROR_DOMAIN,
GSK_ERROR_NO_DOCUMENT,
"No response to your request could be found");
(*content->error_handler) (content, error,
server, request, 404, content->error_data);
g_error_free (error);
return;
serve_error_page:
error = g_error_new (GSK_G_ERROR_DOMAIN,
GSK_ERROR_INTERNAL,
"An internal server error occurred");
(*content->error_handler) (content, error,
server, request, 500, content->error_data);
g_error_free (error);
return;
}
static inline void
try_add_content_type (GskHttpContent *content,
GskHttpRequest *request,
GskHttpResponse *response)
{
const char *type, *subtype;
if (gsk_http_content_get_mime_type (content, request->path, &type, &subtype))
{
gsk_http_header_set_content_type (response, type);
gsk_http_header_set_content_subtype (response, subtype);
}
}
/* --- Raw Data Delivery --- */
typedef struct _DataInfo DataInfo;
struct _DataInfo
{
gconstpointer data;
guint data_len;
gpointer destroy_data;
GDestroyNotify destroy;
};
static GskHttpContentResult
handle_data_request (GskHttpContent *content,
GskHttpContentHandler *handler,
GskHttpServer *server,
GskHttpRequest *request,
GskStream *post_data,
gpointer data)
{
DataInfo *di = data;
GskHttpResponse *response;
GskStream *stream;
response = gsk_http_response_from_request (request,
GSK_HTTP_STATUS_OK,
di->data_len);
handler_ref (handler);
stream = gsk_memory_slab_source_new (di->data, di->data_len,
di->destroy, di->destroy_data);
try_add_content_type (content, request, response);
gsk_http_server_respond (server, request, response, stream);
g_object_unref (response);
g_object_unref (stream);
return GSK_HTTP_CONTENT_OK;
}
static DataInfo *
data_info_alloc ( gconstpointer data,
guint data_len,
gpointer destroy_data,
GDestroyNotify destroy)
{
DataInfo *di = g_new (DataInfo, 1);
di->data = data;
di->data_len = data_len;
di->destroy_data = destroy_data;
di->destroy = destroy;
return di;
}
static void
data_info_destroy (gpointer data)
{
DataInfo *di = data;
if (di->destroy)
di->destroy (di->destroy_data);
g_free (di);
}
/**
* gsk_http_content_add_data:
* @content: the content database to add data to.
* @id: constraints on the query that will get this request.
* @data: the binary data to serve to the client.
* @data_len: length of the data to serve to the client.
* @destroy_data: function that will be passed to the destroy-notify function.
* @destroy: function to be called when this content is unregistered.
*
* Add fixed data to the HTTP content database.
*/
void gsk_http_content_add_data (GskHttpContent *content,
const GskHttpContentId *id,
gconstpointer data,
guint data_len,
gpointer destroy_data,
GDestroyNotify destroy)
{
DataInfo *di = data_info_alloc (data, data_len, destroy_data, destroy);
GskHttpContentHandler *h;
h = gsk_http_content_handler_new (handle_data_request, di, data_info_destroy);
gsk_http_content_add_handler (content, id, h, GSK_HTTP_CONTENT_REPLACE);
gsk_http_content_handler_unref (h);
}
/**
* gsk_http_content_add_data_by_path:
* @content: the content database to add data to.
* @path: raw URI location of the content.
* @data: the binary data to serve to the client.
* @data_len: length of the data to serve to the client.
* @destroy_data: function that will be passed to the destroy-notify function.
* @destroy: function to be called when this content is unregistered.
*
* Add fixed data to the HTTP content database.
*/
void gsk_http_content_add_data_by_path (GskHttpContent *content,
const char *path,
gconstpointer data,
guint data_len,
gpointer destroy_data,
GDestroyNotify destroy)
{
GskHttpContentId id = GSK_HTTP_CONTENT_ID_INIT;
id.path = path;
gsk_http_content_add_data (content, &id, data, data_len, destroy_data, destroy);
}
/* --- Raw File Delivery --- */
typedef struct _FileInfo FileInfo;
struct _FileInfo
{
guint uri_path_len;
char *uri_path;
char *fs_path;
GskHttpContentFileType file_type;
};
static GskHttpContentResult
handle_file_request (GskHttpContent *content,
GskHttpContentHandler *handler,
GskHttpServer *server,
GskHttpRequest *request,
GskStream *post_data,
gpointer data)
{
FileInfo *fi = data;
GskHttpResponse *response;
GskStream *stream;
gint64 size = -1;
struct stat stat_buf;
const char *end;
char *path;
/* compute path (check if it's ok) */
g_return_val_if_fail (memcmp (fi->uri_path, request->path, fi->uri_path_len) == 0,
GSK_HTTP_CONTENT_ERROR);
end = request->path + fi->uri_path_len;
if (memcmp (end, "../", 3) == 0
|| strstr (end, "/../") != NULL
|| g_str_has_suffix (end, "/.."))
{
/* security error */
server_respond_printf (server, request, GSK_HTTP_STATUS_BAD_REQUEST,
"<html><head><title>Security Error</title></head>\n"
"<body>\n"
"<h1>Security Error</h1>\n"
"Attempting to access the path:\n"
"<pre>\n"
" %s\n"
"</pre>\n"
"is not allowed: it may not contain '..'"
"</body>\n"
"</html>\n"
, request->path);
return GSK_HTTP_CONTENT_OK;
}
if (fi->file_type == GSK_HTTP_CONTENT_FILE_EXACT)
path = g_strdup (fi->fs_path);
else
path = g_strdup_printf ("%s/%s", fi->fs_path, request->path);
stream = gsk_stream_fd_new_read_file (path, NULL);
if (stream == NULL)
{
/* serve 404 page */
server_respond_printf (server, request,
GSK_HTTP_STATUS_BAD_REQUEST,
"<html><head><title>Not Found</title></head>\n"
"<body>\n"
"<h1>Not Found</h1>\n"
"Unable to find the file '%s'\n"
"which was requested as the uri '%s'\n"
"</body>\n"
"</html>\n",
path, request->path);
g_free (path);
return GSK_HTTP_CONTENT_OK;
}
if (fstat (GSK_STREAM_FD (stream)->fd, &stat_buf) == 0)
size = stat_buf.st_size;
response = gsk_http_response_from_request (request, GSK_HTTP_STATUS_OK, size);
try_add_content_type (content, request, response);
gsk_http_server_respond (server, request, response, stream);
g_object_unref (response);
g_object_unref (stream);
g_free (path);
return GSK_HTTP_CONTENT_OK;
}
static void
file_info_destroy (gpointer data)
{
FileInfo *fi = data;
g_free (fi->fs_path);
g_free (fi->uri_path);
g_free (fi);
}
/**
* gsk_http_content_add_file:
* @content: the content database to add data to.
* @path: raw URI location of the content.
* @fs_path: location of the data source in the file-system.
* @type: type of file service: this may be an exact file,
* or a directory, or a directory tree.
*
* Add data from files from the native filesystem.
*/
void gsk_http_content_add_file (GskHttpContent *content,
const char *path,
const char *fs_path,
GskHttpContentFileType type)
{
GskHttpContentId id = GSK_HTTP_CONTENT_ID_INIT;
FileInfo *fi = g_new (FileInfo, 1);
Handler *h;
fi->uri_path = g_strdup (path);
fi->uri_path_len = strlen (path);
fi->fs_path = g_strdup (fs_path);
fi->file_type = type;
if (type == GSK_HTTP_CONTENT_FILE_EXACT)
id.path = path;
else
id.path_prefix = path;
h = gsk_http_content_handler_new (handle_file_request, fi, file_info_destroy);
gsk_http_content_add_handler (content, &id, h, GSK_HTTP_CONTENT_REPLACE);
gsk_http_content_handler_unref (h);
}
/**
* gsk_http_content_add_file_by_id:
* @content: the content database to add data to.
* @id: how to match for this file in the URI space.
* This must define either 'path' or 'path_prefix'.
* @fs_path: location of the data source in the file-system.
* @type: type of file service: this may be an exact file,
* or a directory, or a directory tree.
*
* Add data from files from the native filesystem.
*/
void
gsk_http_content_add_file_by_id (GskHttpContent *content,
const GskHttpContentId *id,
const char *fs_path,
GskHttpContentFileType type)
{
FileInfo *fi;
Handler *h;
g_return_if_fail (id->path != NULL || id->path_prefix != NULL);
fi = g_new (FileInfo, 1);
fi->uri_path = g_strdup (id->path_prefix ? id->path_prefix : id->path);
fi->uri_path_len = strlen (fi->uri_path);
fi->fs_path = g_strdup (fs_path);
fi->file_type = type;
h = gsk_http_content_handler_new (handle_file_request, fi, file_info_destroy);
gsk_http_content_add_handler (content, id, h, GSK_HTTP_CONTENT_REPLACE);
gsk_http_content_handler_unref (h);
}
/* --- serving pages --- */
static gboolean
handle_new_request_available (GskHttpServer *server,
gpointer data)
{
GskHttpContent *content = data;
GskHttpRequest *request;
GskStream *post_data = NULL;
if (gsk_http_server_get_request (server, &request, &post_data))
{
gsk_http_content_respond (content, server, request, post_data);
if (post_data)
g_object_unref (post_data);
g_object_unref (request);
}
return TRUE;
}
static gboolean
handle_request_pipe_shutdown (GskHttpServer *server,
gpointer data)
{
return FALSE;
}
/**
* gsk_http_content_manage_server:
* @content: the content database which will handle requests.
* @server: a GskHttpServer to manage.
*
* Handle requests on the given server, using the GskHttpContent.
*/
void
gsk_http_content_manage_server (GskHttpContent *content,
GskHttpServer *server)
{
gsk_http_server_trap (server, handle_new_request_available,
handle_request_pipe_shutdown,
content, NULL);
}
static gboolean
handler_new_connection (GskStream *stream,
gpointer data,
GError **error)
{
GskHttpServer *server = gsk_http_server_new ();
GskHttpContent *content = data;
gsk_http_content_manage_server (content, server);
if (!gsk_stream_attach_pair (GSK_STREAM (server), stream, error))
{
g_object_unref (server);
return FALSE;
}
g_object_unref (server);
g_object_unref (stream);
return TRUE;
}
static void
handler_listener_failed (GError *err,
gpointer data)
{
g_warning ("GskHttpContent: listener failed: %s", err->message);
}
/**
* gsk_http_content_listen:
* @content: the content database which will handle requests.
* @address: the address to bind to, typically in the TCP or Unix namespaces.
* @error: where to put the error if something goes wrong.
*
* Start an HTTP server listening on the given port,
* using content from the given content database.
*
* returns: whether the listen call succeeded.
*/
gboolean
gsk_http_content_listen (GskHttpContent *content,
GskSocketAddress *address,
GError **error)
{
GskStreamListener *listener = gsk_stream_listener_socket_new_bind (address, error);
if (listener == NULL)
return FALSE;
gsk_fd_set_close_on_exec (GSK_STREAM_LISTENER_SOCKET (listener)->fd, TRUE);
gsk_stream_listener_handle_accept (listener, handler_new_connection,
handler_listener_failed,
content, NULL);
return TRUE;
}
syntax highlighted by Code2HTML, v. 0.9.1