libsoup r1175 - in trunk: . docs/reference libsoup tests



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,
+							   &params)) {
+		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]