libsoup r1175 - in trunk: . docs/reference libsoup tests
- From: danw svn gnome org
- To: svn-commits-list gnome org
- Subject: libsoup r1175 - in trunk: . docs/reference libsoup tests
- Date: Wed, 1 Oct 2008 21:12:24 +0000 (UTC)
Author: danw
Date: Wed Oct 1 21:12:24 2008
New Revision: 1175
URL: http://svn.gnome.org/viewvc/libsoup?rev=1175&view=rev
Log:
* libsoup/soup-headers.c (soup_header_parse_param_list)
(soup_header_parse_semi_param_list): Update these to deal with
RFC2231-encoded UTF-8 header params
(soup_header_g_string_append_param): new utility method to do
parameters with quoted-strings (handling escaping) and RFC2231.
* libsoup/soup-auth-digest.c (get_authorization):
* libsoup/soup-auth-domain-basic.c (challenge):
* libsoup/soup-auth-domain-digest.c (challenge): use
soup_header_g_string_append_param so we handle escaping correctly
* libsoup/soup-message-headers.c
(soup_message_headers_get_content_type)
(soup_message_headers_set_content_type)
(soup_message_headers_get_content_disposition)
(soup_message_headers_set_content_disposition): New convenience
methods.
* tests/header-parsing.c (do_rfc2231_tests): new test of RFC2231
encoded header parsing in Content-Disposition.
* tests/get.c (get_url): use
soup_message_headers_get_content_type()
* docs/reference/libsoup-2.4-sections.txt: update
Modified:
trunk/ChangeLog
trunk/docs/reference/libsoup-2.4-sections.txt
trunk/libsoup/soup-auth-digest.c
trunk/libsoup/soup-auth-domain-basic.c
trunk/libsoup/soup-auth-domain-digest.c
trunk/libsoup/soup-headers.c
trunk/libsoup/soup-headers.h
trunk/libsoup/soup-message-headers.c
trunk/libsoup/soup-message-headers.h
trunk/tests/get.c
trunk/tests/header-parsing.c
Modified: trunk/docs/reference/libsoup-2.4-sections.txt
==============================================================================
--- trunk/docs/reference/libsoup-2.4-sections.txt (original)
+++ trunk/docs/reference/libsoup-2.4-sections.txt Wed Oct 1 21:12:24 2008
@@ -107,6 +107,12 @@
SoupExpectation
soup_message_headers_get_expectations
soup_message_headers_set_expectations
+<SUBSECTION>
+soup_message_headers_get_content_type
+soup_message_headers_set_content_type
+<SUBSECTION>
+soup_message_headers_get_content_disposition
+soup_message_headers_set_content_disposition
<SUBSECTION Standard>
SOUP_TYPE_MESSAGE_HEADERS
soup_message_headers_get_type
@@ -583,6 +589,7 @@
soup_header_parse_param_list
soup_header_parse_semi_param_list
soup_header_free_param_list
+soup_header_g_string_append_param
<SUBSECTION>
soup_str_case_equal
soup_str_case_hash
Modified: trunk/libsoup/soup-auth-digest.c
==============================================================================
--- trunk/libsoup/soup-auth-digest.c (original)
+++ trunk/libsoup/soup-auth-digest.c Wed Oct 1 21:12:24 2008
@@ -436,20 +436,29 @@
out = g_string_new ("Digest ");
- /* FIXME: doesn't deal with quotes in the %s strings */
- g_string_append_printf (out, "username=\"%s\", realm=\"%s\", "
- "nonce=\"%s\", uri=\"%s\", response=\"%s\"",
- priv->user, auth->realm, priv->nonce,
- url, response);
-
- if (priv->opaque)
- g_string_append_printf (out, ", opaque=\"%s\"", priv->opaque);
+ soup_header_g_string_append_param (out, "username", priv->user);
+ g_string_append (out, ", ");
+ soup_header_g_string_append_param (out, "realm", auth->realm);
+ g_string_append (out, ", ");
+ soup_header_g_string_append_param (out, "nonce", priv->nonce);
+ g_string_append (out, ", ");
+ soup_header_g_string_append_param (out, "uri", url);
+ g_string_append (out, ", ");
+ soup_header_g_string_append_param (out, "response", response);
+
+ if (priv->opaque) {
+ g_string_append (out, ", ");
+ soup_header_g_string_append_param (out, "opaque", priv->opaque);
+ }
if (priv->qop) {
char *qop = soup_auth_digest_get_qop (priv->qop);
- g_string_append_printf (out, ", cnonce=\"%s\", nc=\"%.8x\", qop=\"%s\"",
- priv->cnonce, priv->nc, qop);
+ g_string_append (out, ", ");
+ soup_header_g_string_append_param (out, "cnonce", priv->cnonce);
+ g_string_append_printf (out, ", nc=\"%.8x\"", priv->nc);
+ g_string_append (out, ", ");
+ soup_header_g_string_append_param (out, "qop", qop);
g_free (qop);
}
Modified: trunk/libsoup/soup-auth-domain-basic.c
==============================================================================
--- trunk/libsoup/soup-auth-domain-basic.c (original)
+++ trunk/libsoup/soup-auth-domain-basic.c Wed Oct 1 21:12:24 2008
@@ -12,6 +12,7 @@
#include <string.h>
#include "soup-auth-domain-basic.h"
+#include "soup-headers.h"
#include "soup-marshal.h"
#include "soup-message.h"
@@ -308,9 +309,11 @@
static char *
challenge (SoupAuthDomain *domain, SoupMessage *msg)
{
- /* FIXME: if realm has '"'s or '\'s in it, need to escape them */
- return g_strdup_printf ("Basic realm=\"%s\"",
- soup_auth_domain_get_realm (domain));
+ GString *challenge;
+
+ challenge = g_string_new ("Basic ");
+ soup_header_g_string_append_param (challenge, "realm", soup_auth_domain_get_realm (domain));
+ return g_string_free (challenge, FALSE);
}
static gboolean
Modified: trunk/libsoup/soup-auth-domain-digest.c
==============================================================================
--- trunk/libsoup/soup-auth-domain-digest.c (original)
+++ trunk/libsoup/soup-auth-domain-digest.c Wed Oct 1 21:12:24 2008
@@ -362,17 +362,12 @@
GString *str;
str = g_string_new ("Digest ");
-
- /* FIXME: escape realm */
- g_string_append_printf (str, "realm=\"%s\", ",
- soup_auth_domain_get_realm (domain));
-
- g_string_append_printf (str, "nonce=\"%lu%lu\", ",
+ soup_header_g_string_append_param (str, "realm", soup_auth_domain_get_realm (domain));
+ g_string_append_printf (str, ", nonce=\"%lu%lu\"",
(unsigned long) msg,
(unsigned long) time (0));
-
- g_string_append_printf (str, "qop=\"auth\", ");
- g_string_append_printf (str, "algorithm=\"MD5\"");
+ g_string_append_printf (str, ", qop=\"auth\"");
+ g_string_append_printf (str, ", algorithm=\"MD5\"");
return g_string_free (str, FALSE);
}
Modified: trunk/libsoup/soup-headers.c
==============================================================================
--- trunk/libsoup/soup-headers.c (original)
+++ trunk/libsoup/soup-headers.c Wed Oct 1 21:12:24 2008
@@ -12,6 +12,7 @@
#include "soup-headers.h"
#include "soup-misc.h"
+#include "soup-uri.h"
static gboolean
soup_headers_parse (const char *str, int len, SoupMessageHeaders *dest)
@@ -602,6 +603,29 @@
*dst = '\0';
}
+static gboolean
+decode_rfc2231 (char *encoded_string)
+{
+ char *q, *decoded;
+
+ q = strchr (encoded_string, '\'');
+ if (!q)
+ return FALSE;
+ if (g_ascii_strncasecmp (encoded_string, "UTF-8",
+ q - encoded_string) != 0)
+ return FALSE;
+
+ q = strchr (q + 1, '\'');
+ if (!q)
+ return FALSE;
+
+ decoded = soup_uri_decode (q + 1);
+ /* strlen(decoded) <= strlen(q + 1) < strlen(encoded_string) */
+ strcpy (encoded_string, decoded);
+ g_free (decoded);
+ return TRUE;
+}
+
static GHashTable *
parse_param_list (const char *header, char delim)
{
@@ -632,7 +656,14 @@
*name_end = '\0';
value = (char *)skip_lws (eq + 1);
- if (*value == '"')
+
+ if (name_end[-1] == '*' && name_end > item + 1) {
+ name_end[-1] = '\0';
+ if (!decode_rfc2231 (value)) {
+ g_free (item);
+ continue;
+ }
+ } else if (*value == '"')
decode_quoted_string (value);
} else
value = NULL;
@@ -654,6 +685,10 @@
* Tokens that don't have an associated value will still be added to
* the resulting hash table, but with a %NULL value.
*
+ * This also handles RFC2231 encoding (which in HTTP is mostly used
+ * for giving UTF8-encoded filenames in the Content-Disposition
+ * header).
+ *
* Return value: a #GHashTable of list elements, which can be freed
* with soup_header_free_param_list().
**/
@@ -673,6 +708,10 @@
* Tokens that don't have an associated value will still be added to
* the resulting hash table, but with a %NULL value.
*
+ * This also handles RFC2231 encoding (which in HTTP is mostly used
+ * for giving UTF8-encoded filenames in the Content-Disposition
+ * header).
+ *
* Return value: a #GHashTable of list elements, which can be freed
* with soup_header_free_param_list().
**/
@@ -694,3 +733,67 @@
{
g_hash_table_destroy (param_list);
}
+
+static void
+append_param_rfc2231 (GString *string, const char *value)
+{
+ char *encoded;
+
+ g_string_append (string, "*=UTF-8''");
+ encoded = soup_uri_encode (value, " *'%()<>@,;:\\\"/[]?=");
+ g_string_append (string, encoded);
+ g_free (encoded);
+}
+
+static void
+append_param_quoted (GString *string, const char *value)
+{
+ int len;
+
+ g_string_append (string, "=\"");
+ while (*value) {
+ while (*value == '\\' || *value == '"') {
+ g_string_append_c (string, '\\');
+ g_string_append_c (string, *value++);
+ }
+ len = strcspn (value, "\\\"");
+ g_string_append_len (string, value, len);
+ value += len;
+ }
+ g_string_append_c (string, '"');
+}
+
+/**
+ * soup_header_g_string_append_param:
+ * @string: a #GString being used to construct an HTTP header value
+ * @name: a parameter name
+ * @value: a parameter value
+ *
+ * Appends something like <literal>@name="@value"</literal> to @string,
+ * taking care to appropriately escape any quotes or backslashes in @value.
+ *
+ * Alternatively, if @value is a non-ASCII UTF-8 string, it will be
+ * appended using RFC2231 syntax. Although in theory this is supposed
+ * to work anywhere in HTTP that uses this style of parameter, in
+ * reality, it can only be used portably with the Content-Disposition
+ * "filename" parameter.
+ **/
+void
+soup_header_g_string_append_param (GString *string, const char *name,
+ const char *value)
+{
+ const char *v;
+
+ g_string_append (string, name);
+
+ for (v = value; *v; v++) {
+ if (*v & 0x80) {
+ if (g_utf8_validate (value, -1, NULL)) {
+ append_param_rfc2231 (string, value);
+ return;
+ } else
+ break;
+ }
+ }
+ append_param_quoted (string, value);
+}
Modified: trunk/libsoup/soup-headers.h
==============================================================================
--- trunk/libsoup/soup-headers.h (original)
+++ trunk/libsoup/soup-headers.h Wed Oct 1 21:12:24 2008
@@ -46,6 +46,10 @@
GHashTable *soup_header_parse_semi_param_list (const char *header);
void soup_header_free_param_list (GHashTable *param_list);
+void soup_header_g_string_append_param (GString *string,
+ const char *name,
+ const char *value);
+
G_END_DECLS
#endif /*SOUP_HEADERS_H*/
Modified: trunk/libsoup/soup-message-headers.c
==============================================================================
--- trunk/libsoup/soup-message-headers.c (original)
+++ trunk/libsoup/soup-message-headers.c Wed Oct 1 21:12:24 2008
@@ -6,8 +6,10 @@
*/
#include <stdio.h>
+#include <string.h>
#include "soup-message-headers.h"
+#include "soup-headers.h"
#include "soup-misc.h"
/**
@@ -44,6 +46,7 @@
SoupEncoding encoding;
goffset content_length;
SoupExpectation expectations;
+ char *content_type;
int ref_count;
};
@@ -94,6 +97,7 @@
g_array_free (hdrs->array, TRUE);
if (hdrs->concat)
g_hash_table_destroy (hdrs->concat);
+ g_free (hdrs->content_type);
g_slice_free (SoupMessageHeaders, hdrs);
}
}
@@ -377,6 +381,7 @@
static void transfer_encoding_setter (SoupMessageHeaders *, const char *);
static void content_length_setter (SoupMessageHeaders *, const char *);
static void expectation_setter (SoupMessageHeaders *, const char *);
+static void content_type_setter (SoupMessageHeaders *, const char *);
static char *
intern_header_locked (const char *name)
@@ -411,6 +416,9 @@
g_hash_table_insert (header_setters,
intern_header_locked ("Expect"),
expectation_setter);
+ g_hash_table_insert (header_setters,
+ intern_header_locked ("Content-Type"),
+ content_type_setter);
}
interned = intern_header_locked (name);
@@ -652,3 +660,186 @@
else
soup_message_headers_remove (hdrs, "Expect");
}
+
+static gboolean
+parse_content_foo (SoupMessageHeaders *hdrs, const char *header_name,
+ char **foo, GHashTable **params)
+{
+ const char *header;
+ char *semi;
+
+ header = soup_message_headers_get (hdrs, header_name);
+ if (!header)
+ return FALSE;
+
+ if (foo) {
+ *foo = g_strdup (header);
+ semi = strchr (*foo, ';');
+ if (semi) {
+ char *p = semi;
+
+ *semi++ = '\0';
+ while (p - 1 > *foo && g_ascii_isspace(p[-1]))
+ *(--p) = '\0';
+ }
+ } else {
+ semi = strchr (header, ';');
+ if (semi)
+ semi++;
+ }
+
+ if (!params)
+ return TRUE;
+
+ if (!semi) {
+ *params = soup_header_parse_semi_param_list ("");
+ return TRUE;
+ }
+
+ *params = soup_header_parse_semi_param_list (semi);
+ return TRUE;
+}
+
+static void
+set_content_foo (SoupMessageHeaders *hdrs, const char *header_name,
+ const char *foo, GHashTable *params)
+{
+ GString *str;
+ GHashTableIter iter;
+ gpointer key, value;
+
+ str = g_string_new (foo);
+ if (params) {
+ g_hash_table_iter_init (&iter, params);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ g_string_append (str, "; ");
+ soup_header_g_string_append_param (str, key, value);
+ }
+ }
+
+ soup_message_headers_replace (hdrs, header_name,
+ g_string_free (str, FALSE));
+}
+
+static void
+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);
+ } 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
+ *
+ * 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).
+ **/
+const char *
+soup_message_headers_get_content_type (SoupMessageHeaders *hdrs,
+ GHashTable **params)
+{
+ if (!hdrs->content_type)
+ return FALSE;
+
+ if (params)
+ parse_content_foo (hdrs, "Content-Type", NULL, params);
+ return hdrs->content_type;
+}
+
+/**
+ * soup_message_headers_set_content_type:
+ * @hdrs: a #SoupMessageHeaders
+ * @content_type: the MIME type
+ * @params: additional parameters, or %NULL
+ *
+ * Sets the "Content-Type" header in @hdrs to @content_type,
+ * optionally with additional parameters specified in @params.
+ **/
+void
+soup_message_headers_set_content_type (SoupMessageHeaders *hdrs,
+ const char *content_type,
+ GHashTable *params)
+{
+ set_content_foo (hdrs, "Content-Type", content_type, params);
+}
+
+/**
+ * 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
+ *
+ * Looks up the "Content-Disposition" header in @hdrs, parses it, and
+ * returns its value in * disposition and * params @params can be
+ * %NULL if you are only interested in the disposition-type.
+ *
+ * In HTTP, the most common use of this header is to set a
+ * disposition-type of "attachment", to suggest to the browser that a
+ * response should be saved to disk rather than displayed in the
+ * browser. If @params contains a "filename" parameter, this is a
+ * suggestion of a filename to use. (If the parameter value in the
+ * header contains an absolute or relative path, libsoup will truncate
+ * it down to just the final path component, so you do not need to
+ * test this yourself.)
+ *
+ * Return value: %TRUE if @hdrs contains a "Content-Disposition"
+ * header, %FALSE if not (in which case * disposition and * params
+ * will be unchanged).
+ **/
+gboolean
+soup_message_headers_get_content_disposition (SoupMessageHeaders *hdrs,
+ char **disposition,
+ GHashTable **params)
+{
+ gpointer orig_key, orig_value;
+
+ if (!parse_content_foo (hdrs, "Content-Disposition",
+ disposition, params))
+ return FALSE;
+
+ /* If there is a filename parameter, make sure it contains
+ * only a single path component
+ */
+ if (params && g_hash_table_lookup_extended (*params, "filename",
+ &orig_key, &orig_value)) {
+ char *filename = strrchr (orig_value, '/');
+
+ if (filename)
+ g_hash_table_insert (*params, orig_key, filename + 1);
+ }
+ return TRUE;
+}
+
+/**
+ * soup_message_headers_set_content_disposition:
+ * @hdrs: a #SoupMessageHeaders
+ * @disposition: the disposition-type
+ * @params: 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.
+ **/
+void
+soup_message_headers_set_content_disposition (SoupMessageHeaders *hdrs,
+ const char *disposition,
+ GHashTable *params)
+{
+ set_content_foo (hdrs, "Content-Disposition", disposition, params);
+}
+
Modified: trunk/libsoup/soup-message-headers.h
==============================================================================
--- trunk/libsoup/soup-message-headers.h (original)
+++ trunk/libsoup/soup-message-headers.h Wed Oct 1 21:12:24 2008
@@ -82,4 +82,17 @@
void soup_message_headers_set_expectations (SoupMessageHeaders *hdrs,
SoupExpectation expectations);
+const char *soup_message_headers_get_content_type (SoupMessageHeaders *hdrs,
+ GHashTable **params);
+void soup_message_headers_set_content_type (SoupMessageHeaders *hdrs,
+ const char *content_type,
+ GHashTable *params);
+
+gboolean soup_message_headers_get_content_disposition (SoupMessageHeaders *hdrs,
+ char **disposition,
+ GHashTable **params);
+void soup_message_headers_set_content_disposition (SoupMessageHeaders *hdrs,
+ const char *disposition,
+ GHashTable *params);
+
#endif /* SOUP_MESSAGE_HEADERS_H */
Modified: trunk/tests/get.c
==============================================================================
--- trunk/tests/get.c (original)
+++ trunk/tests/get.c Wed Oct 1 21:12:24 2008
@@ -196,8 +196,8 @@
return;
close (fd);
- header = soup_message_headers_get (msg->response_headers, "Content-Type");
- if (header && g_ascii_strncasecmp (header, "text/html", 9) != 0)
+ header = soup_message_headers_get_content_type (msg->response_headers, NULL);
+ if (header && g_ascii_strcasecmp (header, "text/html") != 0)
return;
uri = soup_uri_new (url);
Modified: trunk/tests/header-parsing.c
==============================================================================
--- trunk/tests/header-parsing.c (original)
+++ trunk/tests/header-parsing.c Wed Oct 1 21:12:24 2008
@@ -820,6 +820,61 @@
}
}
+#define RFC2231_TEST_FILENAME "t\xC3\xA9st.txt"
+#define RFC2231_TEST_HEADER "attachment; filename*=UTF-8''t%C3%A9st.txt"
+
+static void
+do_rfc2231_tests (void)
+{
+ SoupMessageHeaders *hdrs;
+ GHashTable *params;
+ const char *header, *filename;
+ char *disposition;
+
+ debug_printf (1, "rfc2231 tests\n");
+
+ hdrs = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE);
+ params = g_hash_table_new (g_str_hash, g_str_equal);
+ g_hash_table_insert (params, "filename", RFC2231_TEST_FILENAME);
+ soup_message_headers_set_content_disposition (hdrs, "attachment", params);
+ g_hash_table_destroy (params);
+
+ header = soup_message_headers_get (hdrs, "Content-Disposition");
+ if (!strcmp (header, RFC2231_TEST_HEADER))
+ debug_printf (1, " encoded OK\n");
+ else {
+ debug_printf (1, " encoding FAILED!\n expected: %s\n got: %s\n",
+ RFC2231_TEST_HEADER, header);
+ errors++;
+ }
+
+ soup_message_headers_clear (hdrs);
+ soup_message_headers_append (hdrs, "Content-Disposition",
+ RFC2231_TEST_HEADER);
+ if (!soup_message_headers_get_content_disposition (hdrs,
+ &disposition,
+ ¶ms)) {
+ debug_printf (1, " decoding FAILED!\n could not parse\n");
+ errors++;
+ return;
+ }
+ g_free (disposition);
+
+ filename = g_hash_table_lookup (params, "filename");
+ if (!filename) {
+ debug_printf (1, " decoding FAILED!\n could not file filename\n");
+ errors++;
+ } else if (strcmp (filename, RFC2231_TEST_FILENAME) != 0) {
+ debug_printf (1, " decoding FAILED!\n expected: %s\n got: %s\n",
+ RFC2231_TEST_FILENAME, filename);
+ errors++;
+ } else
+ debug_printf (1, " decoded OK\n");
+
+ g_hash_table_destroy (params);
+ soup_message_headers_free (hdrs);
+}
+
int
main (int argc, char **argv)
{
@@ -828,6 +883,7 @@
do_request_tests ();
do_response_tests ();
do_qvalue_tests ();
+ do_rfc2231_tests ();
test_cleanup ();
return errors != 0;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]