soup-auth-manager: add soup_auth_manager_use_auth()
[platform/upstream/libsoup.git] / libsoup / soup-message-headers.c
index 4ee5a86..876db61 100644 (file)
@@ -5,12 +5,10 @@
  * 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
@@ -33,6 +37,7 @@
 
 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;
@@ -96,27 +101,12 @@ soup_message_headers_free (SoupMessageHeaders *hdrs)
        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:
@@ -137,7 +127,32 @@ soup_message_headers_clear (SoupMessageHeaders *hdrs)
        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);
 }
 
 /**
@@ -146,7 +161,13 @@ soup_message_headers_clear (SoupMessageHeaders *hdrs)
  * @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,
@@ -155,6 +176,28 @@ 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);
@@ -170,7 +213,11 @@ soup_message_headers_append (SoupMessageHeaders *hdrs,
  * @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,
@@ -194,6 +241,20 @@ find_header (SoupHeader *hdr_array, const char *interned_name, int nth)
        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
@@ -209,6 +270,8 @@ soup_message_headers_remove (SoupMessageHeaders *hdrs, const char *name)
        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);
@@ -221,28 +284,72 @@ soup_message_headers_remove (SoupMessageHeaders *hdrs, const char *name)
 }
 
 /**
- * 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);
@@ -271,6 +378,32 @@ soup_message_headers_get (SoupMessageHeaders *hdrs, const char *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
@@ -290,7 +423,8 @@ typedef struct {
 
 /**
  * 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.
@@ -307,9 +441,11 @@ soup_message_headers_iter_init (SoupMessageHeadersIter *iter,
 
 /**
  * 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,
@@ -347,7 +483,7 @@ soup_message_headers_iter_next (SoupMessageHeadersIter *iter,
 /**
  * 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.
@@ -376,7 +512,7 @@ soup_message_headers_foreach (SoupMessageHeaders *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 *);
@@ -403,7 +539,7 @@ intern_header_name (const char *name, SoupHeaderSetter *setter)
 {
        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);
@@ -426,10 +562,26 @@ intern_header_name (const char *name, SoupHeaderSetter *setter)
        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 */
 
@@ -501,13 +653,20 @@ soup_message_headers_get_encoding (SoupMessageHeaders *hdrs)
        /* 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;
@@ -565,8 +724,13 @@ soup_message_headers_set_encoding (SoupMessageHeaders *hdrs,
 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;
 }
 
 /**
@@ -592,8 +756,8 @@ soup_message_headers_set_content_length (SoupMessageHeaders *hdrs,
 {
        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);
 }
@@ -670,17 +834,19 @@ soup_message_headers_set_expectations (SoupMessageHeaders *hdrs,
  * 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
@@ -696,7 +862,7 @@ sort_ranges (gconstpointer a, gconstpointer b)
  * 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
@@ -713,6 +879,8 @@ sort_ranges (gconstpointer a, gconstpointer b)
  * 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,
@@ -720,7 +888,7 @@ 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;
@@ -795,6 +963,8 @@ soup_message_headers_get_ranges (SoupMessageHeaders  *hdrs,
  * @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,
@@ -812,6 +982,8 @@ 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,
@@ -852,6 +1024,8 @@ 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,
@@ -879,6 +1053,8 @@ 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,
@@ -886,7 +1062,7 @@ 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;
 
@@ -927,6 +1103,8 @@ soup_message_headers_get_content_range (SoupMessageHeaders  *hdrs,
  * 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,
@@ -955,7 +1133,7 @@ parse_content_foo (SoupMessageHeaders *hdrs, const char *header_name,
        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;
 
@@ -1013,8 +1191,15 @@ content_type_setter (SoupMessageHeaders *hdrs, const char *value)
 {
        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;
 }
@@ -1022,23 +1207,26 @@ content_type_setter (SoupMessageHeaders *hdrs, const char *value)
 /**
  * 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);
@@ -1049,10 +1237,13 @@ soup_message_headers_get_content_type (SoupMessageHeaders  *hdrs,
  * 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,
@@ -1065,9 +1256,10 @@ 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
@@ -1089,6 +1281,8 @@ soup_message_headers_set_content_type (SoupMessageHeaders  *hdrs,
  * 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,
@@ -1109,7 +1303,7 @@ 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;
 }
@@ -1118,13 +1312,16 @@ soup_message_headers_get_content_disposition (SoupMessageHeaders  *hdrs,
  * 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,