#include "gskhttpheader.h"
#include "gskhttprequest.h"
#include "../gskmacros.h"
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include "../gskerror.h"
static GObjectClass *parent_class = NULL;
enum
{
PROP_REQUEST_0,
PROP_REQUEST_VERB,
PROP_REQUEST_IF_MODIFIED_SINCE,
PROP_REQUEST_USER_AGENT,
PROP_REQUEST_PATH,
PROP_REQUEST_REFERRER,
PROP_REQUEST_HOST,
PROP_REQUEST_MAX_FORWARDS,
PROP_REQUEST_FROM
};
/* --- GskHttpRequest implementation --- */
static void
gsk_http_request_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GskHttpRequest *request = GSK_HTTP_REQUEST (object);
switch (property_id)
{
case PROP_REQUEST_VERB:
request->verb = g_value_get_enum (value);
break;
case PROP_REQUEST_IF_MODIFIED_SINCE:
request->if_modified_since = g_value_get_long (value);
break;
case PROP_REQUEST_USER_AGENT:
gsk_http_header_set_string_val (request, &request->user_agent, value);
break;
case PROP_REQUEST_PATH:
gsk_http_header_set_string_val (request, &request->path, value);
break;
case PROP_REQUEST_REFERRER:
gsk_http_header_set_string_val (request, &request->referrer, value);
break;
case PROP_REQUEST_HOST:
gsk_http_header_set_string_val (request, &request->host, value);
break;
case PROP_REQUEST_MAX_FORWARDS:
request->max_forwards = g_value_get_int (value);
break;
case PROP_REQUEST_FROM:
gsk_http_header_set_string_val (request, &request->from, value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gsk_http_request_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GskHttpRequest *request = GSK_HTTP_REQUEST (object);
switch (property_id)
{
case PROP_REQUEST_VERB:
g_value_set_enum (value, request->verb);
break;
case PROP_REQUEST_IF_MODIFIED_SINCE:
g_value_set_long (value, request->if_modified_since);
break;
case PROP_REQUEST_USER_AGENT:
g_value_set_string (value, request->user_agent);
break;
case PROP_REQUEST_REFERRER:
g_value_set_string (value, request->referrer);
break;
case PROP_REQUEST_PATH:
g_value_set_string (value, request->path);
break;
case PROP_REQUEST_HOST:
g_value_set_string (value, request->host);
break;
case PROP_REQUEST_MAX_FORWARDS:
g_value_set_int (value, request->max_forwards);
break;
case PROP_REQUEST_FROM:
g_value_set_string (value, request->from);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gsk_http_request_finalize (GObject *object)
{
GskHttpRequest *request = GSK_HTTP_REQUEST (object);
#define FREE_LIST(Class, free_func, member) \
G_STMT_START{ \
Class *at, *next; \
for (at = request->member; at != NULL; at = next) \
{ \
next = at->next; \
free_func (at); \
} \
}G_STMT_END
FREE_LIST (GskHttpCharSet, gsk_http_char_set_free, accept_charsets);
FREE_LIST (GskHttpContentEncodingSet, gsk_http_content_encoding_set_free, accept_content_encodings);
FREE_LIST (GskHttpTransferEncodingSet, gsk_http_transfer_encoding_set_free, accept_transfer_encodings);
FREE_LIST (GskHttpMediaTypeSet, gsk_http_media_type_set_free, accept_media_types);
FREE_LIST (GskHttpLanguageSet, gsk_http_language_set_free, accept_languages);
#undef FREE_LIST
g_free (request->path);
g_free (request->host);
if (request->had_if_match)
g_strfreev (request->if_match);
gsk_http_header_free_string (request, request->user_agent);
gsk_http_header_free_string (request, request->referrer);
gsk_http_header_free_string (request, request->from);
if (request->authorization)
gsk_http_authorization_unref (request->authorization);
if (request->proxy_authorization)
gsk_http_authorization_unref (request->proxy_authorization);
if (NULL != request->cache_control)
{
gsk_http_request_cache_directive_free (request->cache_control);
}
g_free (request->ua_color);
g_free (request->ua_os);
g_free (request->ua_cpu);
g_free (request->ua_language);
g_slist_foreach (request->cookies, (GFunc) gsk_http_cookie_free, NULL);
g_slist_free (request->cookies);
parent_class->finalize (object);
}
static void
gsk_http_request_init (GskHttpRequest *request)
{
request->verb = GSK_HTTP_VERB_GET;
request->if_modified_since = (time_t) -1;
request->max_forwards = -1;
request->keep_alive_seconds = -1;
}
static void
gsk_http_request_class_init (GObjectClass *class)
{
parent_class = g_type_class_peek_parent (class);
class->finalize = gsk_http_request_finalize;
class->get_property = gsk_http_request_get_property;
class->set_property = gsk_http_request_set_property;
g_object_class_install_property (class,
PROP_REQUEST_VERB,
g_param_spec_enum ("verb",
_("Verb"),
_("verb"),
GSK_TYPE_HTTP_VERB,
GSK_HTTP_VERB_GET,
G_PARAM_READWRITE));
g_object_class_install_property (class,
PROP_REQUEST_IF_MODIFIED_SINCE,
g_param_spec_long ("if-modified-since",
_("If-Modified-Since"),
_("IMS tag"),
-1, G_MAXLONG, -1,
G_PARAM_READWRITE));
g_object_class_install_property (class,
PROP_REQUEST_USER_AGENT,
g_param_spec_string ("user-agent",
_("User-Agent"),
_("User Agent"),
NULL,
G_PARAM_READWRITE));
g_object_class_install_property (class,
PROP_REQUEST_PATH,
g_param_spec_string ("path",
_("Path"),
_("Path"),
NULL,
G_PARAM_READWRITE));
g_object_class_install_property (class,
PROP_REQUEST_REFERRER,
g_param_spec_string ("referrer",
_("Referrer"),
_("Referrer"),
NULL,
G_PARAM_READWRITE));
g_object_class_install_property (class,
PROP_REQUEST_HOST,
g_param_spec_string ("host",
_("Host"),
_("Hostname"),
NULL,
G_PARAM_READWRITE));
g_object_class_install_property (class,
PROP_REQUEST_MAX_FORWARDS,
g_param_spec_long ("max-forwards",
_("Max-Forwards"),
_("IMax-Forwards"),
-1, 32, -1,
G_PARAM_READWRITE));
}
GType gsk_http_request_get_type()
{
static GType http_request_type = 0;
if (!http_request_type)
{
static const GTypeInfo http_request_info =
{
sizeof(GskHttpRequestClass),
(GBaseInitFunc) NULL,
(GBaseFinalizeFunc) NULL,
(GClassInitFunc) gsk_http_request_class_init,
NULL, /* class_finalize */
NULL, /* class_data */
sizeof (GskHttpRequest),
8, /* n_preallocs */
(GInstanceInitFunc) gsk_http_request_init,
NULL /* value_table */
};
http_request_type = g_type_register_static (GSK_TYPE_HTTP_HEADER,
"GskHttpRequest",
&http_request_info,
0);
}
return http_request_type;
}
/* --- standard constructors --- */
/* Requests. */
/**
* gsk_http_request_new_blank:
*
* Create a new empty HTTP request.
*
* returns: the new request.
*/
GskHttpRequest *gsk_http_request_new_blank (void)
{
return g_object_new (GSK_TYPE_HTTP_REQUEST, NULL);
}
/**
* gsk_http_request_new:
* @verb: what type of request to make.
* @path: path requested.
*
* Create a new simple HTTP request.
*
* returns: the new request.
*/
GskHttpRequest *
gsk_http_request_new (GskHttpVerb verb,
const char *path)
{
return g_object_new (GSK_TYPE_HTTP_REQUEST,
"verb", verb,
"path", path, NULL);
}
/* GskHttpRequest public methods */
/**
* gsk_http_request_set_cache_control:
* @request: the HTTP request header to affect.
* @directive: the new cache control directive, stolen by the HTTP header.
*
* Set the cache-control flags in the header.
* Note that @directive will be freed by the HTTP header when
* it is destroyed.
*/
void
gsk_http_request_set_cache_control (GskHttpRequest *request,
GskHttpRequestCacheDirective *directive)
{
if (NULL != request->cache_control)
{
gsk_http_request_cache_directive_free (request->cache_control);
}
request->cache_control = directive;
}
/**
* gsk_http_request_has_content_body:
* @request: the request to query.
*
* Get whether this header should be accompanied by
* a content-body.
*/
gboolean
gsk_http_request_has_content_body (GskHttpRequest *request)
{
switch (request->verb)
{
case GSK_HTTP_VERB_GET:
case GSK_HTTP_VERB_HEAD:
case GSK_HTTP_VERB_OPTIONS:
case GSK_HTTP_VERB_DELETE:
case GSK_HTTP_VERB_CONNECT: //???
case GSK_HTTP_VERB_TRACE: //???
return FALSE;
case GSK_HTTP_VERB_POST:
case GSK_HTTP_VERB_PUT:
return TRUE;
break;
}
g_warning ("unrecognized HTTP verb %u", request->verb);
return FALSE;
}
/**
* gsk_http_request_add_charsets:
* @header: the request to affect.
* @char_sets: list of a #GskHttpCharSet's to indicate are accepted.
*
* Add Accept-CharSet headers to the header.
* The char-sets will be freed when @header
* is destroyed.
*
* A CharSet is a string representing a Character Set,
* and an optional real quality factor for that particular Character Set.
* (The default is 1.0)
*
* The CharSet "*" matches all other Character Sets.
* If no "*" is given, then it is as though all other character sets
* were given a quality of 0.0.
*
* If no Accept-CharSet header is given, then all character sets
* are equally acceptable.
*
* If a server cannot meet character set requirements,
* it SHOULD response with an error (#GSK_HTTP_STATUS_NOT_ACCEPTABLE)
* but it may also ignore it, and send output in a character-set
* the client has not suggested.
*
* See RFC 2616, Section 14.2.
*/
void
gsk_http_request_add_charsets (GskHttpRequest *header,
GskHttpCharSet *char_sets)
{
GskHttpCharSet *last = header->accept_charsets;
if (last == NULL)
{
header->accept_charsets = char_sets;
return;
}
while (last->next != NULL)
last = last->next;
last->next = char_sets;
}
/**
* gsk_http_request_clear_charsets:
* @header: the request to affect.
*
* Delete all accepted char-sets from the HTTP request.
*
* This has the effect of leaving a server free to use any
* character set.
*/
void
gsk_http_request_clear_charsets (GskHttpRequest *header)
{
GskHttpCharSet *set = header->accept_charsets;
header->accept_charsets = NULL;
while (set != NULL)
{
GskHttpCharSet *next = set->next;
gsk_http_char_set_free (set);
set = next;
}
}
/**
* gsk_http_request_add_content_encodings:
* @header: the request to affect.
* @set: list of a #GskHttpContentEncodingSet's to indicate are acceptable.
* The list is taken over by the header;
* you must not free it or use it further.
*
* Add Accept-Encoding lines to the header.
* Each GskHttpContentEncodingSet represents a single
* possible encoding and an optional associated
* quality factor.
*
* The rules for conduct are the same as for character set,
* with the exception that if no Accept-Encoding line
* is given then the 'identity' encoding should be preferred.
*
* Note that the GSK http server and client handle content
* encoding automatically, and will do the correct thing
* without your intervention.
*
* See RFC 2616, Section 14.3.
*/
void
gsk_http_request_add_content_encodings (GskHttpRequest *header,
GskHttpContentEncodingSet *set)
{
GskHttpContentEncodingSet *last = header->accept_content_encodings;
if (last == NULL)
{
header->accept_content_encodings = set;
return;
}
while (last->next != NULL)
last = last->next;
last->next = set;
}
/**
* gsk_http_request_clear_content_encodings:
* @header: the request to affect.
*
* Delete all accepted encodings from the HTTP request.
*
* This has the effect of leaving a server free to use any
* content encoding.
*/
void
gsk_http_request_clear_content_encodings(GskHttpRequest *header)
{
GskHttpContentEncodingSet *set = header->accept_content_encodings;
header->accept_content_encodings = NULL;
while (set != NULL)
{
GskHttpContentEncodingSet *next = set->next;
gsk_http_content_encoding_set_free (set);
set = next;
}
}
/**
* gsk_http_request_add_transfer_encodings:
* @header: the request to affect.
* @set: list of a #GskHttpTransferEncodingSet's to indicate are acceptable.
* The list is taken over by the header;
* you must not free it or use it further.
*
* The rules for conduct are the same as for character set,
* with the exception that the defaults are
* 'none' for HTTP 1.0 clients,
* and 'none' and 'chunked' for HTTP 1.1 clients.
*
* Note that the GSK http server and client handle content
* encoding automatically, and will do the correct thing
* without your intervention.
*
* This corresponds to the TE: header.
* See RFC 2616, Section 14.39.
*/
void
gsk_http_request_add_transfer_encodings (GskHttpRequest *header,
GskHttpTransferEncodingSet *set)
{
GskHttpTransferEncodingSet *last = header->accept_transfer_encodings;
if (last == NULL)
{
header->accept_transfer_encodings = set;
return;
}
while (last->next != NULL)
last = last->next;
last->next = set;
}
/**
* gsk_http_request_clear_transfer_encodings:
* @header: the request to affect.
*
* Delete all accepted transfer encodings from the HTTP request.
*
* This has the effect of leaving a server free to use just
* no encoding for HTTP 1.0 clients and also 'chunked' for HTTP 1.1 clients.
*/
void
gsk_http_request_clear_transfer_encodings(GskHttpRequest *header)
{
GskHttpTransferEncodingSet *set = header->accept_transfer_encodings;
header->accept_transfer_encodings = NULL;
while (set != NULL)
{
GskHttpTransferEncodingSet *next = set->next;
gsk_http_transfer_encoding_set_free (set);
set = next;
}
}
/**
* gsk_http_request_add_media:
* @header: the request to affect.
* @set: list of a #GskHttpMediaTypeSet's to indicate are acceptable.
*
* Add Accept: headers to the header.
* The media-type-sets will be freed when @header
* is destroyed.
*
* A MediaSet is a range of media accepted,
* with quality factors as for gsk_http_request_add_charsets().
*
* Note that '*' in a subtype applies to all
* media with that major type, but
* if a specific subtype matches, then it's
* quality is given priority.
*
* XXX: Also, there is an accept-extension 'level='...
* find out what it does!
*
* See RFC 2616, Section 14.1.
*/
void
gsk_http_request_add_media (GskHttpRequest *header,
GskHttpMediaTypeSet *set)
{
GskHttpMediaTypeSet *last = header->accept_media_types;
if (last == NULL)
{
header->accept_media_types = set;
return;
}
while (last->next != NULL)
last = last->next;
last->next = set;
}
/**
* gsk_http_request_clear_media:
* @header: the request to affect.
*
* Delete all accepted media-type-sets from the HTTP request.
*/
void
gsk_http_request_clear_media (GskHttpRequest *header)
{
GskHttpMediaTypeSet *set = header->accept_media_types;
header->accept_media_types = NULL;
while (set != NULL)
{
GskHttpMediaTypeSet *next = set->next;
gsk_http_media_type_set_free (set);
set = next;
}
}
/**
* gsk_http_request_set_authorization:
* @request: the request to adjust the authorization for.
* @is_proxy_auth: whether to set the Proxy-Authorization or the Authorization field.
* @auth: the new authorization to use in this request.
*
* Set the authorization for this request.
* This is like a key to get access to certain entities.
*
* Proxy-Authorization is intended to provide access control to the proxy.
* Normal Authorization is passed through a proxy.
* See sections 14.8 for normal Authorization and 14.34.
*/
void
gsk_http_request_set_authorization (GskHttpRequest *request,
gboolean is_proxy_auth,
GskHttpAuthorization *auth)
{
GskHttpAuthorization **dst_auth = is_proxy_auth ? &request->proxy_authorization : &request->authorization;
if (auth)
gsk_http_authorization_ref (auth);
if (*dst_auth)
gsk_http_authorization_unref (*dst_auth);
*dst_auth = auth;
}
/**
* gsk_http_request_peek_authorization:
* @request: the request to query.
* @is_proxy_auth: whether to query information about proxy authorization,
* or normal server authorization.
*
* Get the requested authorization information.
*
* returns: the authorization information, or NULL if none exists (default).
*/
GskHttpAuthorization *
gsk_http_request_peek_authorization (GskHttpRequest *request,
gboolean is_proxy_auth)
{
return is_proxy_auth ? request->proxy_authorization : request->authorization;
}
/**
* gsk_http_request_add_cookie:
* @header: the request to affect.
* @cookie: the cookie to add to the request.
* It will be freed when the request is freed.
*
* Add a Cookie header line to a request.
*
* Cookies are defined in RFC 2965, a draft standard.
*
* [Section 3.3.4] Sending Cookies to the Origin Server.
* When [the user-agent] sends a request
* to an origin server, the user agent includes a Cookie request header
* if it has stored cookies that are applicable to the request, based on
* (1) the request-host and request-port;
* (2) the request-URI;
* (3) the cookie's age.
*/
void
gsk_http_request_add_cookie (GskHttpRequest *header,
GskHttpCookie *cookie)
{
header->cookies = g_slist_append (header->cookies, cookie);
}
/**
* gsk_http_request_remove_cookie:
* @header: the request to affect.
* @cookie: the cookie to remove from the request.
*
* Remove a cookie from the request's list and delete it.
*/
void
gsk_http_request_remove_cookie (GskHttpRequest *header,
GskHttpCookie *cookie)
{
g_return_if_fail (g_slist_find (header->cookies, cookie) != NULL);
header->cookies = g_slist_remove (header->cookies, cookie);
gsk_http_cookie_free (cookie);
}
/**
* gsk_http_request_find_cookie:
* @header: the request to query.
* @key: the key field of the cookie to return.
*
* Find a cookie provided in the request by key.
*
* returns: a pointer to the cookie, or NULL if not found.
*/
GskHttpCookie *
gsk_http_request_find_cookie (GskHttpRequest *header,
const char *key)
{
GSList *at;
for (at = header->cookies; at != NULL; at = at->next)
{
GskHttpCookie *cookie = at->data;
if (strcmp (cookie->key, key) == 0)
return cookie;
}
return NULL;
}
/* TODO: make more efficient */
static char *unescape_cgi (const char *q, GError **error)
{
GString *str = g_string_new ("");
while (*q)
{
if (*q == '%' && q[1] != 0 && q[2] != 0)
{
char hex[3] = { q[1], q[2], 0 };
guint c = strtoul (hex, NULL, 16);
g_string_append_c (str, c);
q += 3;
}
else if (*q == '+')
{
g_string_append_c (str, ' ');
q++;
}
else
g_string_append_c (str, *q++);
}
return g_string_free (str, FALSE);
}
static char *
unescape_cgi_n (const char *q, guint len, GError **error)
{
char *rv = g_malloc (len + 1);
char *at = rv;
while (len > 0)
{
if (*q == '%')
{
char hex[3];
if (len < 3)
{
g_set_error (error, GSK_G_ERROR_DOMAIN,
GSK_ERROR_HTTP_PARSE,
"'%%' string was too short in query value");
g_free (rv);
return NULL;
}
hex[0] = q[1];
hex[1] = q[2];
hex[2] = 0;
*at++ = strtoul (hex, NULL, 16);
q += 3;
len -= 3;
}
else if (*q == '+')
{
*at++ = ' ';
q++;
len--;
}
else
{
*at++ = *q++;
len--;
}
}
*at = 0;
return rv;
}
GHashTable *
gsk_http_request_parse_cgi_query_string (const char *query_string)
{
char **cgi_vars = gsk_http_parse_cgi_query_string (query_string, NULL);
GHashTable *table;
guint i;
if (cgi_vars == NULL)
return NULL;
table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
for (i = 0; cgi_vars[2*i] != NULL; i++)
g_hash_table_insert (table, cgi_vars[2*i+0], cgi_vars[2*i+1]);
g_free (cgi_vars);
return table;
}
/**
* gsk_http_parse_cgi_query_string:
* @query_string: the full path from the HttpRequest.
* @error: the place to put the error if something goes wrong.
* returns: the key-value pairs of CGI data, NULL-terminated.
*
* Parse the CGI key-value pairs from a query.
* The keys are normal alphanumeric strings;
* the values are de-escaped.
*/
char **
gsk_http_parse_cgi_query_string (const char *query_string,
GError **error)
{
const char *q = strchr (query_string, '?');
const char *at;
guint amp_count;
guint i;
guint n_pieces;
char **rv;
guint n_out = 0;
if (q == NULL)
{
g_set_error (error, GSK_G_ERROR_DOMAIN,
GSK_ERROR_HTTP_PARSE,
"no '?' found in CGI query string");
return NULL;
}
amp_count = 0;
at = q + 1;
while (at)
{
at = strchr (at, '&');
if (at == NULL)
break;
/* skip extra ampersands */
while (*(at+1) == '&')
at++;
amp_count++;
at++;
}
n_pieces = amp_count + 1;
rv = g_new (char *, n_pieces * 2 + 1);
at = q + 1;
for (i = 0; i < n_pieces; i++)
{
const char *equalsat = at;
const char *amp;
equalsat = at;
while (*equalsat != '=')
{
if (*equalsat == '&' || *equalsat == 0)
{
rv[2*n_out] = NULL;
g_strfreev (rv);
g_set_error (error, GSK_G_ERROR_DOMAIN,
GSK_ERROR_HTTP_PARSE,
"error parsing '=' query string cgi pairs");
return FALSE;
}
equalsat++;
}
amp = strchr (equalsat + 1, '&');
rv[n_out*2+0] = g_strndup (at, equalsat - at);
if (amp != NULL)
rv[n_out*2+1] = unescape_cgi_n (equalsat + 1, amp - (equalsat + 1), error);
else
rv[n_out*2+1] = unescape_cgi (equalsat + 1, error);
if (rv[n_out*2+1] == NULL)
{
g_strfreev (rv);
return NULL;
}
at = amp ? amp + 1 : NULL;
if (amp != NULL)
{
/* skip extra ampersands */
while (*(amp+1) == '&')
amp++;
}
n_out++;
}
rv[2*n_out+0] = NULL;
return rv;
}
syntax highlighted by Code2HTML, v. 0.9.1