* Copyright (C) 2007, 2008 Red Hat, Inc.
*/
-#include <stdio.h>
#include <string.h>
#include "soup-message-headers.h"
-#include "soup-headers.h"
-#include "soup-misc.h"
+#include "soup.h"
/**
* SECTION:soup-message-headers
**/
/**
+ * SoupMessageHeaders:
+ *
+ * The HTTP message headers associated with a request or response.
+ */
+
+/**
* SoupMessageHeadersType:
* @SOUP_MESSAGE_HEADERS_REQUEST: request headers
* @SOUP_MESSAGE_HEADERS_RESPONSE: response headers
typedef void (*SoupHeaderSetter) (SoupMessageHeaders *, const char *);
static const char *intern_header_name (const char *name, SoupHeaderSetter *setter);
+static void clear_special_headers (SoupMessageHeaders *hdrs);
typedef struct {
const char *name;
if (--hdrs->ref_count == 0) {
soup_message_headers_clear (hdrs);
g_array_free (hdrs->array, TRUE);
- if (hdrs->concat)
- g_hash_table_destroy (hdrs->concat);
- g_free (hdrs->content_type);
+ g_clear_pointer (&hdrs->concat, g_hash_table_destroy);
g_slice_free (SoupMessageHeaders, hdrs);
}
}
-GType
-soup_message_headers_get_type (void)
-{
- static volatile gsize type_volatile = 0;
-
- if (g_once_init_enter (&type_volatile)) {
- GType type = g_boxed_type_register_static (
- g_intern_static_string ("SoupMessageHeaders"),
- (GBoxedCopyFunc) soup_message_headers_copy,
- (GBoxedFreeFunc) soup_message_headers_free);
- g_once_init_leave (&type_volatile, type);
- }
- return type_volatile;
-}
+G_DEFINE_BOXED_TYPE (SoupMessageHeaders, soup_message_headers, soup_message_headers_copy, soup_message_headers_free)
/**
* soup_message_headers_clear:
if (hdrs->concat)
g_hash_table_remove_all (hdrs->concat);
- hdrs->encoding = -1;
+ clear_special_headers (hdrs);
+}
+
+/**
+ * soup_message_headers_clean_connection_headers:
+ * @hdrs: a #SoupMessageHeaders
+ *
+ * Removes all the headers listed in the Connection header.
+ *
+ * Since: 2.36
+ */
+void
+soup_message_headers_clean_connection_headers (SoupMessageHeaders *hdrs)
+{
+ /* RFC 2616 14.10 */
+ const char *connection;
+ GSList *tokens, *t;
+
+ connection = soup_message_headers_get_list (hdrs, "Connection");
+ if (!connection)
+ return;
+
+ tokens = soup_header_parse_list (connection);
+ for (t = tokens; t; t = t->next)
+ soup_message_headers_remove (hdrs, t->data);
+ soup_header_free_list (tokens);
}
/**
* @name: the header name to add
* @value: the new value of @name
*
- * Appends a new header with name @name and value @value to @hdrs.
+ * Appends a new header with name @name and value @value to @hdrs. (If
+ * there is an existing header with name @name, then this creates a
+ * second one, which is only allowed for list-valued headers; see also
+ * soup_message_headers_replace().)
+ *
+ * The caller is expected to make sure that @name and @value are
+ * syntactically correct.
**/
void
soup_message_headers_append (SoupMessageHeaders *hdrs,
SoupHeader header;
SoupHeaderSetter setter;
+ g_return_if_fail (name != NULL);
+ g_return_if_fail (value != NULL);
+
+ /* Setting a syntactically invalid header name or value is
+ * considered to be a programming error. However, it can also
+ * be a security hole, so we want to fail here even if
+ * compiled with G_DISABLE_CHECKS.
+ */
+#ifndef G_DISABLE_CHECKS
+ g_return_if_fail (*name && strpbrk (name, " \t\r\n:") == NULL);
+ g_return_if_fail (strpbrk (value, "\r\n") == NULL);
+#else
+ if (*name && strpbrk (name, " \t\r\n:")) {
+ g_warning ("soup_message_headers_append: Ignoring bad name '%s'", name);
+ return;
+ }
+ if (strpbrk (value, "\r\n")) {
+ g_warning ("soup_message_headers_append: Ignoring bad value '%s'", value);
+ return;
+ }
+#endif
+
header.name = intern_header_name (name, &setter);
header.value = g_strdup (value);
g_array_append_val (hdrs->array, header);
* @name: the header name to replace
* @value: the new value of @name
*
- * Replaces the value of the header @name in @hdrs with @value.
+ * Replaces the value of the header @name in @hdrs with @value. (See
+ * also soup_message_headers_append().)
+ *
+ * The caller is expected to make sure that @name and @value are
+ * syntactically correct.
**/
void
soup_message_headers_replace (SoupMessageHeaders *hdrs,
return -1;
}
+static int
+find_last_header (SoupHeader *hdr_array, guint length, const char *interned_name, int nth)
+{
+ int i;
+
+ for (i = length; i >= 0; i--) {
+ if (hdr_array[i].name == interned_name) {
+ if (nth-- == 0)
+ return i;
+ }
+ }
+ return -1;
+}
+
/**
* soup_message_headers_remove:
* @hdrs: a #SoupMessageHeaders
SoupHeaderSetter setter;
int index;
+ g_return_if_fail (name != NULL);
+
name = intern_header_name (name, &setter);
while ((index = find_header (hdr_array, name, 0)) != -1) {
g_free (hdr_array[index].value);
}
/**
- * soup_message_headers_get:
+ * soup_message_headers_get_one:
* @hdrs: a #SoupMessageHeaders
* @name: header name
*
- * Gets the value of header @name in @hdrs.
+ * Gets the value of header @name in @hdrs. Use this for headers whose
+ * values are <emphasis>not</emphasis> comma-delimited lists, and
+ * which therefore can only appear at most once in the headers. For
+ * list-valued headers, use soup_message_headers_get_list().
*
- * If @name has multiple values in @hdrs, soup_message_headers_get()
- * will concatenate all of the values together, separated by commas.
- * This is sometimes awkward to parse (eg, WWW-Authenticate,
- * Set-Cookie), but you have to be able to deal with it anyway,
- * because an upstream proxy could do the same thing.
+ * If @hdrs does erroneously contain multiple copies of the header, it
+ * is not defined which one will be returned. (Ideally, it will return
+ * whichever one makes libsoup most compatible with other HTTP
+ * implementations.)
+ *
+ * Return value: the header's value or %NULL if not found.
+ *
+ * Since: 2.28
+ **/
+const char *
+soup_message_headers_get_one (SoupMessageHeaders *hdrs, const char *name)
+{
+ SoupHeader *hdr_array = (SoupHeader *)(hdrs->array->data);
+ guint hdr_length = hdrs->array->len;
+ int index;
+
+ g_return_val_if_fail (name != NULL, NULL);
+
+ name = intern_header_name (name, NULL);
+
+ index = find_last_header (hdr_array, hdr_length, name, 0);
+
+ return (index == -1) ? NULL : hdr_array[index].value;
+}
+
+/**
+ * soup_message_headers_get_list:
+ * @hdrs: a #SoupMessageHeaders
+ * @name: header name
+ *
+ * Gets the value of header @name in @hdrs. Use this for headers whose
+ * values are comma-delimited lists, and which are therefore allowed
+ * to appear multiple times in the headers. For non-list-valued
+ * headers, use soup_message_headers_get_one().
+ *
+ * If @name appears multiple times in @hdrs,
+ * soup_message_headers_get_list() will concatenate all of the values
+ * together, separated by commas. This is sometimes awkward to parse
+ * (eg, WWW-Authenticate, Set-Cookie), but you have to be able to deal
+ * with it anyway, because the HTTP spec explicitly states that this
+ * transformation is allowed, and so an upstream proxy could do the
+ * same thing.
*
* Return value: the header's value or %NULL if not found.
+ *
+ * Since: 2.28
**/
const char *
-soup_message_headers_get (SoupMessageHeaders *hdrs, const char *name)
+soup_message_headers_get_list (SoupMessageHeaders *hdrs, const char *name)
{
SoupHeader *hdr_array = (SoupHeader *)(hdrs->array->data);
GString *concat;
char *value;
int index, i;
+ g_return_val_if_fail (name != NULL, NULL);
+
name = intern_header_name (name, NULL);
if (hdrs->concat) {
value = g_hash_table_lookup (hdrs->concat, name);
}
/**
+ * soup_message_headers_get:
+ * @hdrs: a #SoupMessageHeaders
+ * @name: header name
+ *
+ * Gets the value of header @name in @hdrs.
+ *
+ * This method was supposed to work correctly for both single-valued
+ * and list-valued headers, but because some HTTP clients/servers
+ * mistakenly send multiple copies of headers that are supposed to be
+ * single-valued, it sometimes returns incorrect results. To fix this,
+ * the methods soup_message_headers_get_one() and
+ * soup_message_headers_get_list() were introduced, so callers can
+ * explicitly state which behavior they are expecting.
+ *
+ * Return value: as with soup_message_headers_get_list().
+ *
+ * Deprecated: Use soup_message_headers_get_one() or
+ * soup_message_headers_get_list() instead.
+ **/
+const char *
+soup_message_headers_get (SoupMessageHeaders *hdrs, const char *name)
+{
+ return soup_message_headers_get_list (hdrs, name);
+}
+
+/**
* SoupMessageHeadersIter:
*
* An opaque type used to iterate over a %SoupMessageHeaders
/**
* soup_message_headers_iter_init:
- * @iter: a pointer to a %SoupMessageHeadersIter structure
+ * @iter: (out) (transfer none): a pointer to a %SoupMessageHeadersIter
+ * structure
* @hdrs: a %SoupMessageHeaders
*
* Initializes @iter for iterating @hdrs.
/**
* soup_message_headers_iter_next:
- * @iter: a %SoupMessageHeadersIter
- * @name: pointer to a variable to return the header name in
- * @value: pointer to a variable to return the header value in
+ * @iter: (inout) (transfer none): a %SoupMessageHeadersIter
+ * @name: (out) (transfer none): pointer to a variable to return
+ * the header name in
+ * @value: (out) (transfer none): pointer to a variable to return
+ * the header value in
*
* Yields the next name/value pair in the %SoupMessageHeaders being
* iterated by @iter. If @iter has already yielded the last header,
/**
* soup_message_headers_foreach:
* @hdrs: a #SoupMessageHeaders
- * @func: callback function to run for each header
+ * @func: (scope call): callback function to run for each header
* @user_data: data to pass to @func
*
* Calls @func once for each header value in @hdrs.
}
-static GStaticMutex header_pool_mutex = G_STATIC_MUTEX_INIT;
+G_LOCK_DEFINE_STATIC (header_pool);
static GHashTable *header_pool, *header_setters;
static void transfer_encoding_setter (SoupMessageHeaders *, const char *);
{
const char *interned;
- g_static_mutex_lock (&header_pool_mutex);
+ G_LOCK (header_pool);
if (!header_pool) {
header_pool = g_hash_table_new (soup_str_case_hash, soup_str_case_equal);
if (setter)
*setter = g_hash_table_lookup (header_setters, interned);
- g_static_mutex_unlock (&header_pool_mutex);
+ G_UNLOCK (header_pool);
return interned;
}
+static void
+clear_special_headers (SoupMessageHeaders *hdrs)
+{
+ SoupHeaderSetter setter;
+ GHashTableIter iter;
+ gpointer key, value;
+
+ /* Make sure header_setters has been initialized */
+ intern_header_name ("", NULL);
+
+ g_hash_table_iter_init (&iter, header_setters);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ setter = value;
+ setter (hdrs, NULL);
+ }
+}
/* Specific headers */
/* If Transfer-Encoding was set, hdrs->encoding would already
* be set. So we don't need to check that possibility.
*/
- header = soup_message_headers_get (hdrs, "Content-Length");
+ header = soup_message_headers_get_one (hdrs, "Content-Length");
if (header) {
content_length_setter (hdrs, header);
if (hdrs->encoding != -1)
return hdrs->encoding;
}
+ /* Per RFC 2616 4.4, a response body that doesn't indicate its
+ * encoding otherwise is terminated by connection close, and a
+ * request that doesn't indicate otherwise has no body. Note
+ * that SoupMessage calls soup_message_headers_set_encoding()
+ * to override the response body default for our own
+ * server-side messages.
+ */
hdrs->encoding = (hdrs->type == SOUP_MESSAGE_HEADERS_RESPONSE) ?
SOUP_ENCODING_EOF : SOUP_ENCODING_NONE;
return hdrs->encoding;
goffset
soup_message_headers_get_content_length (SoupMessageHeaders *hdrs)
{
- return (hdrs->encoding == SOUP_ENCODING_CONTENT_LENGTH) ?
- hdrs->content_length : 0;
+ SoupEncoding encoding;
+
+ encoding = soup_message_headers_get_encoding (hdrs);
+ if (encoding == SOUP_ENCODING_CONTENT_LENGTH)
+ return hdrs->content_length;
+ else
+ return 0;
}
/**
{
char length[128];
- snprintf (length, sizeof (length), "%" G_GUINT64_FORMAT,
- content_length);
+ g_snprintf (length, sizeof (length), "%" G_GUINT64_FORMAT,
+ content_length);
soup_message_headers_remove (hdrs, "Transfer-Encoding");
soup_message_headers_replace (hdrs, "Content-Length", length);
}
* Represents a byte range as used in the Range header.
*
* If @end is non-negative, then @start and @end represent the bounds
- * of of the range, counting from %0. (Eg, the first 500 bytes would be
- * represented as @start = %0 and @end = %499.)
+ * of of the range, counting from 0. (Eg, the first 500 bytes would be
+ * represented as @start = 0 and @end = 499.)
*
- * If @end is %-1 and @start is non-negative, then this represents a
+ * If @end is -1 and @start is non-negative, then this represents a
* range starting at @start and ending with the last byte of the
* requested resource body. (Eg, all but the first 500 bytes would be
- * @start = %500, and @end = %-1.)
+ * @start = 500, and @end = -1.)
*
- * If @end is %-1 and @start is negative, then it represents a "suffix
+ * If @end is -1 and @start is negative, then it represents a "suffix
* range", referring to the last -@start bytes of the resource body.
- * (Eg, the last 500 bytes would be @start = %-500 and @end = %-1.)
+ * (Eg, the last 500 bytes would be @start = -500 and @end = -1.)
+ *
+ * Since: 2.26
**/
static int
* soup_message_headers_get_ranges:
* @hdrs: a #SoupMessageHeaders
* @total_length: the total_length of the response body
- * @ranges: return location for an array of #SoupRange
+ * @ranges: (out): return location for an array of #SoupRange
* @length: the length of the returned array
*
* Parses @hdrs's Range header and returns an array of the requested
* Return value: %TRUE if @hdrs contained a "Range" header containing
* byte ranges which could be parsed, %FALSE otherwise (in which case
* @range and @length will not be set).
+ *
+ * Since: 2.26
**/
gboolean
soup_message_headers_get_ranges (SoupMessageHeaders *hdrs,
SoupRange **ranges,
int *length)
{
- const char *range = soup_message_headers_get (hdrs, "Range");
+ const char *range = soup_message_headers_get_one (hdrs, "Range");
GSList *range_list, *r;
GArray *array;
char *spec, *end;
* @ranges: an array of #SoupRange
*
* Frees the array of ranges returned from soup_message_headers_get_ranges().
+ *
+ * Since: 2.26
**/
void
soup_message_headers_free_ranges (SoupMessageHeaders *hdrs,
* Sets @hdrs's Range header to request the indicated ranges. (If you
* only want to request a single range, you can use
* soup_message_headers_set_range().)
+ *
+ * Since: 2.26
**/
void
soup_message_headers_set_ranges (SoupMessageHeaders *hdrs,
*
* If you need to request multiple ranges, use
* soup_message_headers_set_ranges().
+ *
+ * Since: 2.26
**/
void
soup_message_headers_set_range (SoupMessageHeaders *hdrs,
*
* Return value: %TRUE if @hdrs contained a "Content-Range" header
* containing a byte range which could be parsed, %FALSE otherwise.
+ *
+ * Since: 2.26
**/
gboolean
soup_message_headers_get_content_range (SoupMessageHeaders *hdrs,
goffset *end,
goffset *total_length)
{
- const char *header = soup_message_headers_get (hdrs, "Content-Range");
+ const char *header = soup_message_headers_get_one (hdrs, "Content-Range");
goffset length;
char *p;
* Sets @hdrs's Content-Range header according to the given values.
* (Note that @total_length is the total length of the entire resource
* that this is a range of, not simply @end - @start + 1.)
+ *
+ * Since: 2.26
**/
void
soup_message_headers_set_content_range (SoupMessageHeaders *hdrs,
const char *header;
char *semi;
- header = soup_message_headers_get (hdrs, header_name);
+ header = soup_message_headers_get_one (hdrs, header_name);
if (!header)
return FALSE;
{
g_free (hdrs->content_type);
if (value) {
- parse_content_foo (hdrs, "Content-Type",
- &hdrs->content_type, NULL);
+ char *content_type, *p;
+
+ parse_content_foo (hdrs, "Content-Type", &content_type, NULL);
+ p = strpbrk (content_type, " /");
+ if (!p || *p != '/' || strpbrk (p + 1, " /")) {
+ g_free (content_type);
+ hdrs->content_type = NULL;
+ } else
+ hdrs->content_type = content_type;
} else
hdrs->content_type = NULL;
}
/**
* soup_message_headers_get_content_type:
* @hdrs: a #SoupMessageHeaders
- * @params: return location for the Content-Type parameters (eg,
- * "charset"), or %NULL
+ * @params: (out) (element-type utf8 utf8) (allow-none) (transfer full):
+ * return location for the Content-Type parameters (eg, "charset"), or
+ * %NULL
*
* Looks up the "Content-Type" header in @hdrs, parses it, and returns
* its value in *@content_type and *@params. @params can be %NULL if you
* are only interested in the content type itself.
*
- * Return value: %TRUE if @hdrs contains a "Content-Type" header,
- * %FALSE if not (in which case *@content_type and *@params will be
- * unchanged).
+ * Return value: a string with the value of the "Content-Type" header
+ * or NULL if @hdrs does not contain that header or it cannot be
+ * parsed (in which case *@params will be unchanged).
+ *
+ * Since: 2.26
**/
const char *
soup_message_headers_get_content_type (SoupMessageHeaders *hdrs,
GHashTable **params)
{
if (!hdrs->content_type)
- return FALSE;
+ return NULL;
if (params)
parse_content_foo (hdrs, "Content-Type", NULL, params);
* soup_message_headers_set_content_type:
* @hdrs: a #SoupMessageHeaders
* @content_type: the MIME type
- * @params: additional parameters, or %NULL
+ * @params: (allow-none) (element-type utf8 utf8): additional
+ * parameters, or %NULL
*
* Sets the "Content-Type" header in @hdrs to @content_type,
* optionally with additional parameters specified in @params.
+ *
+ * Since: 2.26
**/
void
soup_message_headers_set_content_type (SoupMessageHeaders *hdrs,
/**
* soup_message_headers_get_content_disposition:
* @hdrs: a #SoupMessageHeaders
- * @disposition: return location for the disposition-type, or %NULL
- * @params: return location for the Content-Disposition parameters, or
- * %NULL
+ * @disposition: (out) (transfer full): return location for the
+ * disposition-type, or %NULL
+ * @params: (out) (transfer full) (element-type utf8 utf8): return
+ * location for the Content-Disposition parameters, or %NULL
*
* Looks up the "Content-Disposition" header in @hdrs, parses it, and
* returns its value in *@disposition and *@params. @params can be
* Return value: %TRUE if @hdrs contains a "Content-Disposition"
* header, %FALSE if not (in which case *@disposition and *@params
* will be unchanged).
+ *
+ * Since: 2.26
**/
gboolean
soup_message_headers_get_content_disposition (SoupMessageHeaders *hdrs,
char *filename = strrchr (orig_value, '/');
if (filename)
- g_hash_table_insert (*params, orig_key, filename + 1);
+ g_hash_table_insert (*params, g_strdup (orig_key), filename + 1);
}
return TRUE;
}
* soup_message_headers_set_content_disposition:
* @hdrs: a #SoupMessageHeaders
* @disposition: the disposition-type
- * @params: additional parameters, or %NULL
+ * @params: (allow-none) (element-type utf8 utf8): additional
+ * parameters, or %NULL
*
* Sets the "Content-Disposition" header in @hdrs to @disposition,
* optionally with additional parameters specified in @params.
*
* See soup_message_headers_get_content_disposition() for a discussion
* of how Content-Disposition is used in HTTP.
+ *
+ * Since: 2.26
**/
void
soup_message_headers_set_content_disposition (SoupMessageHeaders *hdrs,