libsoup r1135 - in trunk: . libsoup



Author: danw
Date: Wed Apr  9 03:02:02 2008
New Revision: 1135
URL: http://svn.gnome.org/viewvc/libsoup?rev=1135&view=rev

Log:
	Initial HTTP cookie support imported from development git repo,
	including patches from Xan Lopez.

	TODO: make sure the logic in soup_cookie_jar_get_cookies() is
	right. Add a test program to tests/.

	* libsoup/soup-cookie.c: Code for parsing and generating HTTP
	cookies.

	* libsoup/soup-cookie-jar.c: Code for managing SoupCookies and
	integrating cookie management with a SoupSession.

	* libsoup/soup-date.c (soup_date_is_past): New, checks if a
	SoupDate refers to a time in the past

	* libsoup/soup-dns.c (soup_dns_is_ip_address): New, checks if a
	string is a valid IP address

	* libsoup/soup-headers.c (soup_header_parse_semi_param_list): New,
	like soup_header_parse_param_list, but for semicolon-delimited
	data.


Added:
   trunk/libsoup/soup-cookie-jar.c
   trunk/libsoup/soup-cookie-jar.h
   trunk/libsoup/soup-cookie.c
   trunk/libsoup/soup-cookie.h
Modified:
   trunk/ChangeLog
   trunk/libsoup/Makefile.am
   trunk/libsoup/soup-date.c
   trunk/libsoup/soup-date.h
   trunk/libsoup/soup-dns.c
   trunk/libsoup/soup-dns.h
   trunk/libsoup/soup-headers.c
   trunk/libsoup/soup-headers.h
   trunk/libsoup/soup-types.h
   trunk/libsoup/soup.h

Modified: trunk/libsoup/Makefile.am
==============================================================================
--- trunk/libsoup/Makefile.am	(original)
+++ trunk/libsoup/Makefile.am	Wed Apr  9 03:02:02 2008
@@ -51,6 +51,8 @@
 	soup-auth-domain.h	\
 	soup-auth-domain-basic.h  \
 	soup-auth-domain-digest.h \
+	soup-cookie.h		\
+	soup-cookie-jar.h	\
 	soup-date.h		\
 	soup-form.h		\
 	soup-headers.h		\
@@ -109,6 +111,8 @@
 	soup-auth-manager-ntlm.c	\
 	soup-connection.h		\
 	soup-connection.c		\
+	soup-cookie.c			\
+	soup-cookie-jar.c		\
 	soup-date.c			\
 	soup-dns.h			\
 	soup-dns.c			\

Added: trunk/libsoup/soup-cookie-jar.c
==============================================================================
--- (empty file)
+++ trunk/libsoup/soup-cookie-jar.c	Wed Apr  9 03:02:02 2008
@@ -0,0 +1,311 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-cookie-jar.c
+ *
+ * Copyright (C) 2008 Red Hat, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+
+#include "soup-cookie.h"
+#include "soup-cookie-jar.h"
+#include "soup-date.h"
+#include "soup-message.h"
+#include "soup-session-feature.h"
+#include "soup-uri.h"
+
+/**
+ * SECTION:soup-cookie-jar
+ * @short_description: 
+ *
+ * A #SoupCookieJar stores #SoupCookie<!-- -->s and arrange for them
+ * to be sent with the appropriate #SoupMessage<!-- -->s.
+ * #SoupCookieJar implements #SoupSessionFeature, so you can add a
+ * cookie jar to a session with soup_session_add_feature() or
+ * soup_session_add_feature_by_type().
+ *
+ * Note that the base #SoupCookieJar class does not support any form
+ * of long-term cookie persistence.
+ **/
+
+static void soup_cookie_jar_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
+static void request_queued (SoupSessionFeature *feature, SoupSession *session,
+			    SoupMessage *msg);
+static void request_started (SoupSessionFeature *feature, SoupSession *session,
+			     SoupMessage *msg, SoupSocket *socket);
+static void request_unqueued (SoupSessionFeature *feature, SoupSession *session,
+			      SoupMessage *msg);
+
+G_DEFINE_TYPE_WITH_CODE (SoupCookieJar, soup_cookie_jar, G_TYPE_OBJECT,
+			 G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
+						soup_cookie_jar_session_feature_init));
+
+typedef struct {
+	GHashTable *domains;
+} SoupCookieJarPrivate;
+#define SOUP_COOKIE_JAR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_COOKIE_JAR, SoupCookieJarPrivate))
+
+static void
+soup_cookie_jar_init (SoupCookieJar *jar)
+{
+	SoupCookieJarPrivate *priv = SOUP_COOKIE_JAR_GET_PRIVATE (jar);
+
+	priv->domains = g_hash_table_new_full (g_str_hash, g_str_equal,
+					       g_free, NULL);
+}
+
+static void
+finalize (GObject *object)
+{
+	SoupCookieJarPrivate *priv = SOUP_COOKIE_JAR_GET_PRIVATE (object);
+	GHashTableIter iter;
+	gpointer key, value;
+
+	g_hash_table_iter_init (&iter, priv->domains);
+	while (g_hash_table_iter_next (&iter, &key, &value))
+		soup_cookies_free (value);
+	g_hash_table_destroy (priv->domains);
+
+	G_OBJECT_CLASS (soup_cookie_jar_parent_class)->finalize (object);
+}
+
+static void
+soup_cookie_jar_class_init (SoupCookieJarClass *jar_class)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (jar_class);
+
+	g_type_class_add_private (jar_class, sizeof (SoupCookieJarPrivate));
+
+	object_class->finalize = finalize;
+}
+
+static void
+soup_cookie_jar_session_feature_init (SoupSessionFeatureInterface *feature_interface,
+				      gpointer interface_data)
+{
+	feature_interface->request_queued = request_queued;
+	feature_interface->request_started = request_started;
+	feature_interface->request_unqueued = request_unqueued;
+}
+
+/**
+ * soup_cookie_jar_new:
+ *
+ * Creates a new #SoupCookieJar.
+ *
+ * Returns: a new #SoupCookieJar
+ **/
+SoupCookieJar *
+soup_cookie_jar_new (void) 
+{
+	return g_object_new (SOUP_TYPE_COOKIE_JAR, NULL);
+}
+
+/**
+ * soup_cookie_jar_save:
+ * @jar: a SoupCookieJar
+ *
+ * Tells @jar to save the state of its (non-session) cookies to some
+ * sort of permanent storage.
+ **/
+void
+soup_cookie_jar_save (SoupCookieJar *jar)
+{
+	if (SOUP_COOKIE_JAR_GET_CLASS (jar)->save)
+		SOUP_COOKIE_JAR_GET_CLASS (jar)->save (jar);
+}
+
+/**
+ * soup_cookie_jar_get_cookies:
+ * @jar: a #SoupCookieJar
+ * @uri: a #SoupURI
+ * @for_http: whether or not the return value is being passed directly
+ * to an HTTP operation
+ *
+ * Retrieves (in Cookie-header form) the list of cookies that would
+ * be sent with a request to @uri.
+ *
+ * If @for_http is %TRUE, the return value will include cookies marked
+ * "HttpOnly" (that is, cookies that the server wishes to keep hidden
+ * from client-side scripting operations such as the JavaScript
+ * document.cookies property). Since #SoupCookieJar sets the Cookie
+ * header itself when making the actual HTTP request, you should
+ * almost certainly be setting @for_http to %FALSE if you are calling
+ * this.
+ *
+ * Return value: the cookies, in string form, or %NULL if there are no
+ * cookies for @uri.
+ **/
+char *
+soup_cookie_jar_get_cookies (SoupCookieJar *jar, SoupURI *uri,
+			     gboolean for_http)
+{
+	SoupCookieJarPrivate *priv;
+	GSList *cookies, *domain_cookies;
+	char *domain, *cur, *next, *result;
+
+	g_return_val_if_fail (SOUP_IS_COOKIE_JAR (jar), NULL);
+	priv = SOUP_COOKIE_JAR_GET_PRIVATE (jar);
+
+	/* The logic here is a little weird, but the plan is that if
+	 * uri->host is "www.foo.com", we will end up looking up
+	 * cookies for ".www.foo.com", "www.foo.com", ".foo.com", and
+	 * ".com", in that order. (Logic stolen from Mozilla.)
+	 */
+	cookies = NULL;
+	domain = cur = g_strdup_printf (".%s", uri->host);
+	next = domain + 1;
+	do {
+		domain_cookies = g_hash_table_lookup (priv->domains, cur);
+		while (domain_cookies) {
+			SoupCookie *cookie = domain_cookies->data;
+
+			if (soup_cookie_applies_to_uri (cookie, uri) &&
+			    (for_http || !cookie->http_only))
+				cookies = g_slist_append (cookies, cookie);
+			domain_cookies = domain_cookies->next;
+		}
+		cur = next;
+		if (cur)
+			next = strchr (cur + 1, '.');
+	} while (cur);
+	g_free (domain);
+
+	if (cookies) {
+		/* FIXME: sort? */
+		result = soup_cookies_to_cookie_header (cookies);
+		g_slist_free (cookies);
+		return result;
+	} else
+		return NULL;
+}
+
+static GSList *
+get_cookies_for_domain (SoupCookieJar *jar, const char *domain)
+{
+	SoupCookieJarPrivate *priv = SOUP_COOKIE_JAR_GET_PRIVATE (jar);
+	GSList *cookies, *orig_cookies, *c;
+	SoupCookie *cookie;
+
+	cookies = g_hash_table_lookup (priv->domains, domain);
+	c = orig_cookies = cookies;
+	while (c) {
+		cookie = c->data;
+		c = c->next;
+		if (cookie->expires && soup_date_is_past (cookie->expires)) {
+			cookies = g_slist_remove (cookies, cookie);
+			soup_cookie_free (cookie);
+		}
+	}
+
+	if (cookies != orig_cookies)
+		g_hash_table_insert (priv->domains, g_strdup (domain), cookies);
+	return cookies;
+}
+
+static void
+set_cookie (SoupCookieJar *jar, SoupCookie *cookie)
+{
+	SoupCookieJarPrivate *priv = SOUP_COOKIE_JAR_GET_PRIVATE (jar);
+	GSList *old_cookies, *oc, *prev = NULL;
+	SoupCookie *old_cookie;
+
+	old_cookies = get_cookies_for_domain (jar, cookie->domain);
+	for (oc = old_cookies; oc; oc = oc->next) {
+		old_cookie = oc->data;
+		if (!strcmp (cookie->name, old_cookie->name)) {
+			/* The new cookie is a replacement for an old
+			 * cookie. It might be pre-expired, but we
+			 * don't worry about that here;
+			 * get_cookies_for_domain() will delete it
+			 * later.
+			 */
+			soup_cookie_free (old_cookie);
+			oc->data = cookie;
+			return;
+		}
+		prev = oc;
+	}
+
+	/* The new cookie is... a new cookie */
+	if (cookie->expires && soup_date_is_past (cookie->expires))
+		soup_cookie_free (cookie);
+	else if (prev)
+		prev = g_slist_append (prev, cookie);
+	else {
+		old_cookies = g_slist_append (NULL, cookie);
+		g_hash_table_insert (priv->domains, g_strdup (cookie->domain),
+				     old_cookies);
+	}
+}
+
+/**
+ * soup_cookie_jar_set_cookie:
+ * @jar: a #SoupCookieJar
+ * @uri: the URI setting the cookie
+ * @cookie: the stringified cookie to set
+ *
+ * Adds @cookie to @jar, exactly as though it had appeared in a
+ * Set-Cookie header returned from a request to @uri.
+ **/
+void
+soup_cookie_jar_set_cookie (SoupCookieJar *jar, SoupURI *uri,
+			    const char *cookie)
+{
+	SoupCookie *soup_cookie;
+
+	g_return_if_fail (SOUP_IS_COOKIE_JAR (jar));
+	g_return_if_fail (cookie != NULL);
+
+	soup_cookie = soup_cookie_parse (cookie, uri);
+	set_cookie (jar, soup_cookie);
+	/* set_cookie will steal or free soup_cookie */
+}
+
+static void
+process_set_cookie_header (SoupMessage *msg, gpointer user_data)
+{
+	SoupCookieJar *jar = user_data;
+	GSList *new_cookies, *nc;
+
+	new_cookies = soup_cookies_from_response (msg);
+	for (nc = new_cookies; nc; nc = nc->next)
+		set_cookie (jar, nc->data);
+	g_slist_free (new_cookies);
+}
+
+static void
+request_queued (SoupSessionFeature *feature, SoupSession *session,
+		SoupMessage *msg)
+{
+	soup_message_add_header_handler (msg, "got-headers",
+					 "Set-Cookie",
+					 G_CALLBACK (process_set_cookie_header),
+					 feature);
+}
+
+static void
+request_started (SoupSessionFeature *feature, SoupSession *session,
+		 SoupMessage *msg, SoupSocket *socket)
+{
+	SoupCookieJar *jar = SOUP_COOKIE_JAR (feature);
+	char *cookies;
+
+	cookies = soup_cookie_jar_get_cookies (jar, soup_message_get_uri (msg), TRUE);
+	soup_message_headers_replace (msg->request_headers,
+				      "Cookie", cookies);
+	g_free (cookies);
+}
+
+static void
+request_unqueued (SoupSessionFeature *feature, SoupSession *session,
+		  SoupMessage *msg)
+{
+	g_signal_handlers_disconnect_by_func (msg, process_set_cookie_header, feature);
+}
+

Added: trunk/libsoup/soup-cookie-jar.h
==============================================================================
--- (empty file)
+++ trunk/libsoup/soup-cookie-jar.h	Wed Apr  9 03:02:02 2008
@@ -0,0 +1,48 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2008 Red Hat, Inc.
+ */
+
+#ifndef SOUP_COOKIE_JAR_H
+#define SOUP_COOKIE_JAR_H 1
+
+#include <libsoup/soup-types.h>
+
+#define SOUP_TYPE_COOKIE_JAR            (soup_cookie_jar_get_type ())
+#define SOUP_COOKIE_JAR(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_COOKIE_JAR, SoupCookieJar))
+#define SOUP_COOKIE_JAR_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_COOKIE_JAR, SoupCookieJarClass))
+#define SOUP_IS_COOKIE_JAR(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_COOKIE_JAR))
+#define SOUP_IS_COOKIE_JAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SOUP_TYPE_COOKIE_JAR))
+#define SOUP_COOKIE_JAR_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_COOKIE_JAR, SoupCookieJarClass))
+
+typedef struct {
+	GObject parent;
+
+} SoupCookieJar;
+
+typedef struct {
+	GObjectClass parent_class;
+
+	void (*save) (SoupCookieJar *jar);
+
+	/* Padding for future expansion */
+	void (*_libsoup_reserved1) (void);
+	void (*_libsoup_reserved2) (void);
+	void (*_libsoup_reserved3) (void);
+	void (*_libsoup_reserved4) (void);
+} SoupCookieJarClass;
+
+GType          soup_cookie_jar_get_type    (void);
+
+SoupCookieJar *soup_cookie_jar_new         (void);
+
+void           soup_cookie_jar_save        (SoupCookieJar *jar);
+
+char          *soup_cookie_jar_get_cookies (SoupCookieJar *jar,
+					    SoupURI       *uri,
+					    gboolean       for_http);
+void           soup_cookie_jar_set_cookie  (SoupCookieJar *jar,
+					    SoupURI       *uri,
+					    const char    *cookie);
+
+#endif /* SOUP_COOKIE_JAR_H */

Added: trunk/libsoup/soup-cookie.c
==============================================================================
--- (empty file)
+++ trunk/libsoup/soup-cookie.c	Wed Apr  9 03:02:02 2008
@@ -0,0 +1,856 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-cookie.c
+ *
+ * Copyright (C) 2007 Red Hat, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "soup-cookie.h"
+#include "soup-date.h"
+#include "soup-dns.h"
+#include "soup-headers.h"
+#include "soup-message.h"
+#include "soup-message-headers.h"
+#include "soup-uri.h"
+
+/**
+ * SECTION:soup-cookie
+ * @short_description: HTTP Cookies
+ * @see_also: #SoupMessage
+ *
+ * #SoupCookie implements HTTP cookies, primarily as described by
+ * <ulink
+ * url="http://wp.netscape.com/newsref/std/cookie_spec.html";>the
+ * original Netscape cookie specification</ulink>, but with slight
+ * modifications based on <ulink
+ * url="http://www.ietf.org/rfc/rfc2109.txt";>RFC 2109</ulink>, <ulink
+ * url="http://msdn2.microsoft.com/en-us/library/ms533046.aspx";>Microsoft's
+ * HttpOnly extension attribute</ulink>, and observed real-world usage
+ * (and, in particular, based on what Firefox does).
+ **/
+
+/**
+ * SoupCookie:
+ * @name: the cookie name
+ * @value: the cookie value
+ * @domain: the "domain" attribute, or %NULL
+ * @path: the "path" attribute, or %NULL
+ * @expires: the cookie expiration time, or %NULL for a session cookie
+ * @secure: %TRUE if the cookie should only be tranferred over SSL
+ * @http_only: %TRUE if the cookie should not be exposed to scripts
+ *
+ * An HTTP cookie.
+ *
+ * @name and @value will be set for all cookies. If the cookie is
+ * generated from a string that appears to have no name, then @name
+ * will be the empty string.
+ *
+ * @domain and @path give the host or domain, and path within that
+ * host/domain, to restrict this cookie to. If @domain starts with
+ * ".", that indicates a domain (which matches the string after the
+ * ".", or any hostname that has @domain as a suffix). Otherwise, it
+ * is a hostname and must match exactly.
+ *
+ * @expires will be non-%NULL if the cookie uses either the original
+ * "expires" attribute, or the "max-age" attribute specified in RFC
+ * 2109. If @expires is %NULL, it indicates that neither "expires" nor
+ * "max-age" was specified, and the cookie expires at the end of the
+ * session.
+ * 
+ * If @http_only is set, the cookie should not be exposed to untrusted
+ * code (eg, javascript), so as to minimize the danger posed by
+ * cross-site scripting attacks.
+ **/
+
+/* Our Set-Cookie grammar is something like the following, in terms of
+ * RFC 2616 BNF:
+ *
+ * set-cookie             =  "Set-Cookie:" cookies
+ * cookies                =  #cookie
+ *
+ * cookie                 =  [ NAME "=" ] VALUE *(";" [ cookie-av ] )
+ * NAME                   =  cookie-attr
+ * VALUE                  =  cookie-comma-value
+ * cookie-av              =  "Domain" "=" cookie-value
+ *                        |  "Expires" "=" cookie-date-value
+ *                        |  "HttpOnly"
+ *                        |  "Max-Age" "=" cookie-value
+ *                        |  "Path" "=" cookie-value
+ *                        |  "Secure"
+ *                        |  cookie-attr [ "=" cookie-value ]
+ *
+ * cookie-attr            =  1*<any CHAR except CTLs or ";" or "," or "=">
+ *
+ * cookie-value           =  cookie-raw-value | cookie-quoted-string
+ * cookie-raw-value       =  *<any CHAR except CTLs or ";" or ",">
+ *
+ * cookie-comma-value     =  cookie-raw-comma-value | cookie-quoted-string
+ * cookie-raw-comma-value =  *<any CHAR except CTLs or ";">
+ *
+ * cookie-date-value      =  cookie-raw-date-value | cookie-quoted-string
+ * cookie-raw-date-value  =  [ token "," ] cookie-raw-value
+ *
+ * cookie-quoted-string   =  quoted-string [ cookie-raw-value ]
+ *
+ * NAME is optional, as described in
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=169091#c16
+ *
+ * When VALUE is a quoted-string, the quotes (and any internal
+ * backslashes) are considered part of the value, and returned
+ * literally. When other cookie-values or cookie-comma-values are
+ * quoted-strings, the quotes are NOT part of the value. If a
+ * cookie-value or cookie-comma-value has trailing junk after the
+ * quoted-string, it is discarded.
+ *
+ * Note that VALUE and "Expires" are allowed to have commas in them,
+ * but anywhere else, a comma indicates a new cookie.
+ *
+ * The literal strings in cookie-av ("Domain", "Expires", etc) are all
+ * case-insensitive. Unrecognized cookie attributes are discarded.
+ *
+ * Cookies are allowed to have excess ";"s, and in particular, can
+ * have a trailing ";".
+ */
+
+static gboolean
+domain_matches (const char *domain, const char *host)
+{
+	char *match;
+	int dlen;
+
+	if (!g_ascii_strcasecmp (domain, host))
+		return TRUE;
+	if (*domain != '.')
+		return FALSE;
+	dlen = strlen (domain);
+	while ((match = strstr (host, domain))) {
+		if (!match[dlen])
+			return TRUE;
+		host = match + 1;
+	}
+	return FALSE;
+}
+
+static inline const char *
+skip_lws (const char *s)
+{
+	while (g_ascii_isspace (*s))
+		s++;
+	return s;
+}
+
+static inline const char *
+unskip_lws (const char *s, const char *start)
+{
+	while (s > start && g_ascii_isspace (*(s - 1)))
+		s--;
+	return s;
+}
+
+#define is_attr_ender(ch) ((ch) < ' ' || (ch) == ';' || (ch) == ',' || (ch) == '=')
+#define is_value_ender(ch, allow_comma) ((ch) < ' ' || (ch) == ';' || (!(allow_comma) && (ch) == ','))
+
+static char *
+parse_value (const char **val_p, gboolean keep_quotes, gboolean allow_comma)
+{
+	const char *start, *end, *p;
+	char *value, *q;
+
+	p = *val_p;
+	if (*p == '=')
+		p++;
+	start = skip_lws (p);
+	if (*start == '"') {
+		for (p = start + 1; *p && *p != '"'; p++) {
+			if (*p == '\\' && *(p + 1))
+				p++;
+		}
+		if (keep_quotes)
+			value = g_strndup (start, p - start + 1);
+		else {
+			value = g_malloc (p - (start + 1) + 1);
+			for (p = start + 1, q = value; *p && *p != '"'; p++, q++) {
+				if (*p == '\\' && *(p + 1))
+					p++;
+				*q = *p;
+			}
+			*q = '\0';
+		}
+
+		/* Skip anything after the quoted-string */
+		while (!is_value_ender (*p, FALSE))
+			p++;
+	} else {
+		for (p = start; !is_value_ender (*p, allow_comma); p++)
+			;
+		end = unskip_lws (p, start);
+		value = g_strndup (start, end - start);
+	}
+
+	*val_p = p;
+	return value;
+}
+
+static SoupDate *
+parse_date (const char **val_p)
+{
+	const char *start, *end, *p;
+	char *value;
+	SoupDate *date;
+
+	p = *val_p + 1;
+	start = skip_lws (p);
+	if (*start == '"')
+		value = parse_value (&p, FALSE, FALSE);
+	else {
+		gboolean allow_comma = TRUE;
+
+		for (p = start; !is_value_ender (*p, allow_comma); p++) {
+			if (*p == ' ')
+				allow_comma = FALSE;
+		}
+		end = unskip_lws (p, start);
+		value = g_strndup (start, end - start);
+	}
+
+	date = soup_date_new_from_string (value);
+	g_free (value);
+	*val_p = p;
+	return date;
+}
+
+static SoupCookie *
+parse_one_cookie (const char **header_p, SoupURI *origin)
+{
+	const char *header = *header_p, *p;
+	const char *start, *end;
+	gboolean has_value;
+	SoupCookie *cookie;	
+
+	cookie = g_slice_new0 (SoupCookie);
+
+	/* Parse the NAME */
+	start = skip_lws (header);
+	for (p = start; !is_attr_ender (*p); p++)
+		;
+	if (*p == '=') {
+		end = unskip_lws (p, start);
+		cookie->name = g_strndup (start, end - start);
+	} else {
+		/* No NAME; Set cookie->name to "" and then rewind to
+		 * re-parse the string as a VALUE.
+		 */
+		cookie->name = g_strdup ("");
+		p = start;
+	}
+
+	/* Parse the VALUE */
+	cookie->value = parse_value (&p, TRUE, TRUE);
+
+	/* Parse attributes */
+	while (*p == ';') {
+		start = skip_lws (p + 1);
+		for (p = start; !is_attr_ender (*p); p++)
+			;
+		end = unskip_lws (p, start);
+
+		has_value = (*p == '=');
+#define MATCH_NAME(name) ((end - start == strlen (name)) && !g_ascii_strncasecmp (start, name, end - start))
+
+		if (MATCH_NAME ("domain") && has_value) {
+			cookie->domain = parse_value (&p, FALSE, FALSE);
+		} else if (MATCH_NAME ("expires") && has_value) {
+			cookie->expires = parse_date (&p);
+		} else if (MATCH_NAME ("httponly") && !has_value) {
+			cookie->http_only = TRUE;
+		} else if (MATCH_NAME ("max-age") && has_value) {
+			char *max_age = parse_value (&p, FALSE, FALSE);
+			soup_cookie_set_max_age (cookie, strtoul (max_age, NULL, 10));
+			g_free (max_age);
+		} else if (MATCH_NAME ("path") && has_value) {
+			cookie->path = parse_value (&p, FALSE, FALSE);
+		} else if (MATCH_NAME ("secure") && !has_value) {
+			cookie->secure = TRUE;
+		} else {
+			/* Ignore unknown attributes, but we still have
+			 * to skip over the value.
+			 */
+			if (has_value)
+				g_free (parse_value (&p, TRUE, FALSE));
+		}
+	}
+
+	if (*p == ',') {
+		p = skip_lws (p + 1);
+		if (*p)
+			*header_p = p;
+	} else
+		*header_p = NULL;
+
+	if (cookie->domain) {
+		/* Domain must have at least one '.' (not counting an
+		 * initial one. (We check this now, rather than
+		 * bailing out sooner, because we don't want to force
+		 * any cookies after this one in the Set-Cookie header
+		 * to be discarded.)
+		 */
+		if (!strchr (cookie->domain + 1, '.')) {
+			soup_cookie_free (cookie);
+			return NULL;
+		}
+
+		/* If the domain string isn't an IP addr, and doesn't
+		 * start with a '.', prepend one.
+		 */
+		if (!soup_dns_is_ip_address (cookie->domain) &&
+		    cookie->domain[0] != '.') {
+			char *tmp = g_strdup_printf (".%s", cookie->domain);
+			g_free (cookie->domain);
+			cookie->domain = tmp;
+		}
+	}
+
+	if (origin) {
+		/* Sanity-check domain */
+		if (cookie->domain) {
+			if (!domain_matches (cookie->domain, origin->host)) {
+				soup_cookie_free (cookie);
+				return NULL;
+			}
+		} else
+			cookie->domain = g_strdup (origin->host);
+
+		/* The original cookie spec didn't say that pages
+		 * could only set cookies for paths they were under.
+		 * RFC 2109 adds that requirement, but some sites
+		 * depend on the old behavior
+		 * (https://bugzilla.mozilla.org/show_bug.cgi?id=156725#c20).
+		 * So we don't check the path.
+		 */
+
+		if (!cookie->path) {
+			char *slash;
+
+			cookie->path = g_strdup (origin->path);
+			slash = strrchr (cookie->path, '/');
+			if (slash)
+				*slash = '\0';
+		}
+	}
+
+	return cookie;
+}
+
+/**
+ * soup_cookie_new:
+ * @name: cookie name
+ * @value: cookie value
+ * @domain: cookie domain, or %NULL
+ * @path: cookie path, or %NULL
+ * @max_age: max age of the cookie, or -1 for a session cookie
+ *
+ * Creates a new #SoupCookie with the given attributes. (Use
+ * soup_cookie_set_secure() and soup_cookie_set_http_only() if you
+ * need to set those attributes on the returned cookie.)
+ *
+ * @max_age is used to set the "expires" attribute on the cookie; pass
+ * -1 to not include the attribute (indicating that the cookie expires
+ * with the current session), 0 for an already-expired cookie, or a
+ * lifetime in seconds. You can use the constants
+ * %SOUP_COOKIE_MAX_AGE_ONE_HOUR, %SOUP_COOKIE_MAX_AGE_ONE_DAY,
+ * %SOUP_COOKIE_MAX_AGE_ONE_WEEK and %SOUP_COOKIE_MAX_AGE_ONE_YEAR (or
+ * multiples thereof) to calculate this value. (If you really care
+ * about setting the exact time that the cookie will expire, use
+ * soup_cookie_set_expires().)
+ *
+ * Return value: a new #SoupCookie.
+ **/
+SoupCookie *
+soup_cookie_new (const char *name, const char *value,
+		 const char *domain, const char *path,
+		 int max_age)
+{
+	SoupCookie *cookie;	
+
+	cookie = g_slice_new0 (SoupCookie);
+	cookie->name = g_strdup (name);
+	cookie->value = g_strdup (value);
+	cookie->domain = g_strdup (domain);
+	cookie->path = g_strdup (path);
+	soup_cookie_set_max_age (cookie, max_age);
+
+	return cookie;
+}
+
+/**
+ * soup_cookie_parse:
+ * @cookie: a cookie string (eg, the value of a Set-Cookie header)
+ * @origin: origin of the cookie, or %NULL
+ *
+ * Parses @cookie and returns a #SoupCookie. (If @cookie contains
+ * multiple cookies, only the first one will be parsed.)
+ *
+ * If @cookie does not have "path" or "domain" attributes, they will
+ * be defaulted from @origin. If @origin is %NULL, path will default
+ * to "/", but domain will be left as %NULL. Note that this is not a
+ * valid state for a #SoupCookie, and you will need to fill in some
+ * appropriate string for the domain if you want to actually make use
+ * of the cookie.
+ *
+ * Return value: a new #SoupCookie, or %NULL if it could not be
+ * parsed, or contained an illegal "domain" attribute for a cookie
+ * originating from @origin.
+ **/
+SoupCookie *
+soup_cookie_parse (const char *cookie, SoupURI *origin)
+{
+	return parse_one_cookie (&cookie, origin);
+}
+
+/**
+ * soup_cookie_set_name:
+ * @cookie: a #SoupCookie
+ * @name: the new name
+ *
+ * Sets @cookie's name to @name
+ **/
+void
+soup_cookie_set_name (SoupCookie *cookie, const char *name)
+{
+	g_free (cookie->name);
+	cookie->name = g_strdup (name);
+}
+
+/**
+ * soup_cookie_set_value:
+ * @cookie: a #SoupCookie
+ * @value: the new value
+ *
+ * Sets @cookie's value to @value
+ **/
+void
+soup_cookie_set_value (SoupCookie *cookie, const char *value)
+{
+	g_free (cookie->value);
+	cookie->value = g_strdup (value);
+}
+
+/**
+ * soup_cookie_set_domain:
+ * @cookie: a #SoupCookie
+ * @domain: the new domain
+ *
+ * Sets @cookie's domain to @domain
+ **/
+void
+soup_cookie_set_domain (SoupCookie *cookie, const char *domain)
+{
+	g_free (cookie->domain);
+	cookie->domain = g_strdup (domain);
+}
+
+/**
+ * soup_cookie_set_path:
+ * @cookie: a #SoupCookie
+ * @path: the new path
+ *
+ * Sets @cookie's path to @path
+ **/
+void
+soup_cookie_set_path (SoupCookie *cookie, const char *path)
+{
+	g_free (cookie->path);
+	cookie->path = g_strdup (path);
+}
+
+/**
+ * soup_cookie_set_max_age:
+ * @cookie: a #SoupCookie
+ * @max_age: the new max age
+ *
+ * Sets @cookie's max age to @max_age. If @max_age is -1, the cookie
+ * is a session cookie, and will expire at the end of the client's
+ * session. Otherwise, it is the number of seconds until the cookie
+ * expires. You can use the constants %SOUP_COOKIE_MAX_AGE_ONE_HOUR,
+ * %SOUP_COOKIE_MAX_AGE_ONE_DAY, %SOUP_COOKIE_MAX_AGE_ONE_WEEK and
+ * %SOUP_COOKIE_MAX_AGE_ONE_YEAR (or multiples thereof) to calculate
+ * this value. (A value of 0 indicates that the cookie should be
+ * considered already-expired.)
+ *
+ * (This sets the same property as soup_cookie_set_expires().)
+ **/
+void
+soup_cookie_set_max_age (SoupCookie *cookie, int max_age)
+{
+	if (cookie->expires)
+		soup_date_free (cookie->expires);
+
+	if (max_age == -1)
+		cookie->expires = NULL;
+	else if (max_age == 0) {
+		/* Use a date way in the past, to protect against
+		 * clock skew.
+		 */
+		cookie->expires = soup_date_new (1970, 1, 1, 0, 0, 0);
+	} else
+		cookie->expires = soup_date_new_from_now (max_age);
+}
+
+/**
+ * soup_cookie_set_expires:
+ * @cookie: a #SoupCookie
+ * @expires: the new expiration time, or %NULL
+ *
+ * Sets @cookie's expiration time to @expires. If @expires is %NULL,
+ * @cookie will be a session cookie and will expire at the end of the
+ * client's session.
+ *
+ * (This sets the same property as soup_cookie_set_max_age().)
+ **/
+void
+soup_cookie_set_expires (SoupCookie *cookie, SoupDate *expires)
+{
+	if (cookie->expires)
+		soup_date_free (cookie->expires);
+
+	if (expires)
+		cookie->expires = soup_date_copy (expires);
+	else
+		cookie->expires = NULL;
+}
+
+/**
+ * soup_cookie_set_secure:
+ * @cookie: a #SoupCookie
+ * @secure: the new value for the secure attribute
+ *
+ * Sets @cookie's secure attribute to @secure. If %TRUE, @cookie will
+ * only be transmitted from the client to the server over secure
+ * (https) connections.
+ **/
+void
+soup_cookie_set_secure (SoupCookie *cookie, gboolean secure)
+{
+	cookie->secure = secure;
+}
+
+/**
+ * soup_cookie_set_http_only:
+ * @cookie: a #SoupCookie
+ * @http_only: the new value for the HttpOnly attribute
+ *
+ * Sets @cookie's HttpOnly attribute to @http_only. If %TRUE, @cookie
+ * will be marked as "http only", meaning it should not be exposed to
+ * web page scripts or other untrusted code.
+ **/
+void
+soup_cookie_set_http_only (SoupCookie *cookie, gboolean http_only)
+{
+	cookie->http_only = http_only;
+}
+
+static void
+serialize_cookie (SoupCookie *cookie, GString *header, gboolean set_cookie)
+{
+	if (header->len) {
+		if (set_cookie)
+			g_string_append (header, ", ");
+		else
+			g_string_append (header, "; ");
+	}
+
+	g_string_append (header, cookie->name);
+	g_string_append (header, "=");
+	g_string_append (header, cookie->value);
+	if (!set_cookie)
+		return;
+
+	if (cookie->expires) {
+		char *timestamp;
+
+		g_string_append (header, "; expires=");
+		timestamp = soup_date_to_string (cookie->expires,
+						 SOUP_DATE_COOKIE);
+		g_string_append (header, timestamp);
+		g_free (timestamp);
+	}
+	if (cookie->path) {
+		g_string_append (header, "; path=");
+		g_string_append (header, cookie->path);
+	}
+	if (cookie->domain) {
+		g_string_append (header, "; domain=");
+		g_string_append (header, cookie->domain);
+	}
+	if (cookie->secure)
+		g_string_append (header, "; secure");
+	if (cookie->secure)
+		g_string_append (header, "; HttpOnly");
+}
+
+/**
+ * soup_cookie_to_set_cookie_header:
+ * @cookie: a #SoupCookie
+ *
+ * Serializes @cookie in the format used by the Set-Cookie header
+ * (ie, for sending a cookie from a #SoupServer to a client).
+ *
+ * Return value: the header
+ **/
+char *
+soup_cookie_to_set_cookie_header (SoupCookie *cookie)
+{
+	GString *header = g_string_new (NULL);
+
+	serialize_cookie (cookie, header, TRUE);
+	return g_string_free (header, FALSE);
+}
+
+/**
+ * soup_cookie_to_cookie_header:
+ * @cookie: a #SoupCookie
+ *
+ * Serializes @cookie in the format used by the Cookie header (ie, for
+ * returning a cookie from a #SoupSession to a server).
+ *
+ * Return value: the header
+ **/
+char *
+soup_cookie_to_cookie_header (SoupCookie *cookie)
+{
+	GString *header = g_string_new (NULL);
+
+	serialize_cookie (cookie, header, FALSE);
+	return g_string_free (header, FALSE);
+}
+
+/**
+ * soup_cookie_free:
+ * @cookie: a #SoupCookie
+ *
+ * Frees @cookie
+ **/
+void
+soup_cookie_free (SoupCookie *cookie)
+{
+	g_return_if_fail (cookie != NULL);
+
+	g_free (cookie->name);
+	g_free (cookie->value);
+	g_free (cookie->domain);
+	g_free (cookie->path);
+
+	g_slice_free (SoupCookie, cookie);
+}
+
+/**
+ * soup_cookies_from_response:
+ * @msg: a #SoupMessage containing a "Set-Cookie" response header
+ *
+ * Parses @msg's Set-Cookie response headers and returns a #GSList of
+ * #SoupCookie<!-- -->s. Cookies that do not specify "path" or
+ * "domain" attributes will have their values defaulted from @origin.
+ *
+ * Return value: a #GSList of #SoupCookie<!-- -->s, which can be freed
+ * with soup_cookies_free().
+ **/
+GSList *
+soup_cookies_from_response (SoupMessage *msg)
+{
+	SoupURI *origin;
+	const char *name, *value;
+	SoupCookie *cookie;
+	GSList *cookies = NULL;
+	SoupMessageHeadersIter iter;
+
+	origin = soup_message_get_uri (msg);
+
+	/* Although parse_one_cookie tries to deal with multiple
+	 * comma-separated cookies, it is impossible to do that 100%
+	 * reliably, so we try to pass it separate Set-Cookie headers
+	 * instead.
+	 */
+	soup_message_headers_iter_init (&iter, msg->response_headers);
+	while (soup_message_headers_iter_next (&iter, &name, &value)) {
+		if (g_ascii_strcasecmp (name, "Set-Cookie") != 0)
+			continue;
+
+		while (value) {
+			cookie = parse_one_cookie (&value, origin);
+			if (cookie)
+				cookies = g_slist_prepend (cookies, cookie);
+		}
+	}
+	return g_slist_reverse (cookies);
+}
+
+/**
+ * soup_cookies_from_request:
+ * @msg: a #SoupMessage containing a "Cookie" request header
+ *
+ * Parses @msg's Cookie request header and returns a #GSList of
+ * #SoupCookie<!-- -->s. As the "Cookie" header, unlike "Set-Cookie",
+ * only contains cookie names and values, none of the other
+ * #SoupCookie fields will be filled in. (Thus, you can't generally
+ * pass a cookie returned from this method directly to
+ * soup_cookies_to_response().)
+ *
+ * Return value: a #GSList of #SoupCookie<!-- -->s, which can be freed
+ * with soup_cookies_free().
+ **/
+GSList *
+soup_cookies_from_request (SoupMessage *msg)
+{
+	SoupCookie *cookie;
+	GSList *cookies = NULL;
+	GHashTable *params;
+	GHashTableIter iter;
+	gpointer name, value;
+
+	params = soup_header_parse_semi_param_list (soup_message_headers_get (msg->request_headers, "Cookie"));
+	g_hash_table_iter_init (&iter, params);
+	while (g_hash_table_iter_next (&iter, &name, &value)) {
+		cookie = soup_cookie_new (name, value, NULL, NULL, 0);
+		cookies = g_slist_prepend (cookies, cookie);
+	}
+	soup_header_free_param_list (params);
+
+	return g_slist_reverse (cookies);
+}
+
+/**
+ * soup_cookies_to_response:
+ * @cookies: a #GSList of #SoupCookie
+ * @msg: a #SoupMessage
+ *
+ * Appends a "Set-Cookie" response header to @msg for each cookie in
+ * @cookies. (This is in addition to any other "Set-Cookie" headers
+ * @msg may already have.)
+ **/
+void
+soup_cookies_to_response (GSList *cookies, SoupMessage *msg)
+{
+	GString *header;
+
+	header = g_string_new (NULL);
+	while (cookies) {
+		serialize_cookie (cookies->data, header, TRUE);
+		soup_message_headers_append (msg->response_headers,
+					     "Set-Cookie", header->str);
+		g_string_truncate (header, 0);
+		cookies = cookies->next;
+	}
+	g_string_free (header, TRUE);
+}
+
+/**
+ * soup_cookies_to_request:
+ * @cookies: a #GSList of #SoupCookie
+ * @msg: a #SoupMessage
+ *
+ * Adds the name and value of each cookie in @cookies to @msg's
+ * "Cookie" request. (If @msg already has a "Cookie" request header,
+ * these cookies will be appended to the cookies already present. Be
+ * careful that you do not append the same cookies twice, eg, when
+ * requeuing a message.)
+ **/
+void
+soup_cookies_to_request (GSList *cookies, SoupMessage *msg)
+{
+	GString *header;
+
+	header = g_string_new (soup_message_headers_get (msg->request_headers,
+							 "Cookie"));
+	while (cookies) {
+		serialize_cookie (cookies->data, header, FALSE);
+		cookies = cookies->next;
+	}
+	soup_message_headers_replace (msg->request_headers,
+				      "Cookie", header->str);
+	g_string_free (header, TRUE);
+}
+
+/**
+ * soup_cookies_free:
+ * @cookies: a #GSList of #SoupCookie
+ *
+ * Frees @cookies.
+ **/
+void
+soup_cookies_free (GSList *cookies)
+{
+	GSList *c;
+
+	for (c = cookies; c; c = c->next)
+		soup_cookie_free (c->data);
+	g_slist_free (cookies);
+}
+
+/**
+ * soup_cookies_to_cookie_header:
+ * @cookies: a #GSList of #SoupCookie
+ *
+ * Serializes a #GSList of #SoupCookie into a string suitable for
+ * setting as the value of the "Cookie" header.
+ *
+ * Return value: the serialization of @cookies
+ **/
+char *
+soup_cookies_to_cookie_header (GSList *cookies)
+{
+	GString *str;
+
+	g_return_val_if_fail (cookies != NULL, NULL);
+
+	str = g_string_new (NULL);
+	while (cookies) {
+		serialize_cookie (cookies->data, str, FALSE);
+		cookies = cookies->next;
+	}
+
+	return g_string_free (str, FALSE);
+}
+
+/**
+ * soup_cookie_applies_to_uri:
+ * @cookie: a #SoupCookie
+ * @uri: a #SoupURI
+ *
+ * Tests if @cookie should be sent to @uri.
+ *
+ * (At the moment, this does not check that @cookie's domain matches
+ * @uri, because it assumes that the caller has already done that.
+ * But don't rely on that; it may change in the future.)
+ *
+ * Return value: %TRUE if @cookie should be sent to @uri, %FALSE if
+ * not
+ **/
+gboolean
+soup_cookie_applies_to_uri (SoupCookie *cookie, SoupURI *uri)
+{
+	int plen;
+
+	if (cookie->secure && uri->scheme != SOUP_URI_SCHEME_HTTPS)
+		return FALSE;
+
+	if (cookie->expires && soup_date_is_past (cookie->expires))
+		return FALSE;
+
+	/* The spec claims "/foo would match /foobar", but fortunately
+	 * no one is really that crazy.
+	 */
+	plen = strlen (cookie->path);
+	if (strncmp (cookie->path, uri->path, plen) != 0)
+		return FALSE;
+	if (uri->path[plen] && uri->path[plen] != '/')
+		return FALSE;
+
+	return TRUE;
+}

Added: trunk/libsoup/soup-cookie.h
==============================================================================
--- (empty file)
+++ trunk/libsoup/soup-cookie.h	Wed Apr  9 03:02:02 2008
@@ -0,0 +1,75 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* 
+ * Copyright 2007, 2008 Red Hat, Inc.
+ */
+
+#ifndef SOUP_COOKIE_H
+#define SOUP_COOKIE_H 1
+
+#include <libsoup/soup-types.h>
+
+G_BEGIN_DECLS
+
+struct _SoupCookie {
+	char     *name;
+	char     *value;
+	char     *domain;
+	char     *path;
+	SoupDate *expires;
+	gboolean  secure;
+	gboolean  http_only;
+};
+
+#define SOUP_COOKIE_MAX_AGE_ONE_HOUR (60 * 60)
+#define SOUP_COOKIE_MAX_AGE_ONE_DAY  (SOUP_COOKIE_MAX_AGE_ONE_HOUR * 24)
+#define SOUP_COOKIE_MAX_AGE_ONE_WEEK (SOUP_COOKIE_MAX_AGE_ONE_DAY * 7)
+#define SOUP_COOKIE_MAX_AGE_ONE_YEAR (SOUP_COOKIE_MAX_AGE_ONE_DAY * 365.2422)
+
+SoupCookie *soup_cookie_new                     (const char  *name,
+						 const char  *value,
+						 const char  *domain,
+						 const char  *path,
+						 int          max_age);
+SoupCookie *soup_cookie_parse                   (const char  *header,
+						 SoupURI     *origin);
+
+void        soup_cookie_set_name                (SoupCookie  *cookie,
+						 const char  *name);
+void        soup_cookie_set_value               (SoupCookie  *cookie,
+						 const char  *value);
+void        soup_cookie_set_domain              (SoupCookie  *cookie,
+						 const char  *domain);
+void        soup_cookie_set_path                (SoupCookie  *cookie,
+						 const char  *path);
+void        soup_cookie_set_max_age             (SoupCookie  *cookie,
+						 int          max_age);
+void        soup_cookie_set_expires             (SoupCookie  *cookie,
+						 SoupDate    *expires);
+void        soup_cookie_set_secure              (SoupCookie  *cookie,
+						 gboolean     secure);
+void        soup_cookie_set_http_only           (SoupCookie  *cookie,
+						 gboolean     secure);
+
+char       *soup_cookie_to_set_cookie_header    (SoupCookie  *cookie);
+char       *soup_cookie_to_cookie_header        (SoupCookie  *cookie);
+
+void        soup_cookie_free                    (SoupCookie  *cookie);
+
+GSList     *soup_cookies_from_response          (SoupMessage *msg);
+GSList     *soup_cookies_from_request           (SoupMessage *msg);
+
+void        soup_cookies_to_response            (GSList      *cookies,
+						 SoupMessage *msg);
+void        soup_cookies_to_request             (GSList      *cookies,
+						 SoupMessage *msg);
+
+void        soup_cookies_free                   (GSList      *cookies);
+
+char       *soup_cookies_to_cookie_header       (GSList      *cookies);
+
+gboolean    soup_cookie_applies_to_uri          (SoupCookie  *cookie,
+						 SoupURI     *uri);
+
+G_END_DECLS
+
+#endif /* SOUP_COOKIE_H */

Modified: trunk/libsoup/soup-date.c
==============================================================================
--- trunk/libsoup/soup-date.c	(original)
+++ trunk/libsoup/soup-date.c	Wed Apr  9 03:02:02 2008
@@ -566,6 +566,24 @@
 }
 
 /**
+ * soup_date_is_past:
+ * @date: a #SoupDate
+ *
+ * Determines if @date is in the past.
+ *
+ * Return value: %TRUE if @date is in the past
+ **/
+gboolean
+soup_date_is_past (SoupDate *date)
+{
+	/* optimization */
+	if (date->year < 2008)
+		return TRUE;
+
+	return soup_date_to_time_t (date) < time (NULL);
+}
+
+/**
  * soup_date_copy:
  * @date: a #SoupDate
  *

Modified: trunk/libsoup/soup-date.h
==============================================================================
--- trunk/libsoup/soup-date.h	(original)
+++ trunk/libsoup/soup-date.h	Wed Apr  9 03:02:02 2008
@@ -12,7 +12,7 @@
 
 G_BEGIN_DECLS
 
-typedef struct {
+struct _SoupDate {
 	int      year;
 	int      month;
 	int      day;
@@ -23,7 +23,7 @@
 
 	gboolean utc;
 	int      offset;
-} SoupDate;
+};
 
 typedef enum {
 	SOUP_DATE_HTTP = 1,
@@ -52,6 +52,8 @@
 				     SoupDateFormat  format);
 time_t    soup_date_to_time_t       (SoupDate       *date);
 
+gboolean  soup_date_is_past         (SoupDate       *date);
+
 SoupDate *soup_date_copy            (SoupDate       *date);
 void      soup_date_free            (SoupDate       *date);
 

Modified: trunk/libsoup/soup-dns.c
==============================================================================
--- trunk/libsoup/soup-dns.c	(original)
+++ trunk/libsoup/soup-dns.c	Wed Apr  9 03:02:02 2008
@@ -303,6 +303,31 @@
 	}
 }
 
+gboolean
+soup_dns_is_ip_address (const char *name)
+{
+	struct sockaddr_in sin;
+#ifdef HAVE_IPV6
+	struct sockaddr_in6 sin6;
+
+	if (inet_pton (AF_INET, name, &sin.sin_addr) > 0 ||
+	    inet_pton (AF_INET6, name, &sin6.sin6_addr) > 0)
+		return TRUE;
+#else /* !HAVE_IPV6 */
+#if defined(HAVE_INET_PTON)
+	if (inet_pton (AF_INET, name, &sin.sin_addr) > 0)
+		return TRUE;
+#elif defined(HAVE_INET_ATON)
+	if (inet_aton (name, &sin.sin_addr) != 0)
+		return TRUE;
+#else
+	if (inet_addr (entry->entry_name) != INADDR_NONE)
+		return TRUE;
+#endif
+#endif /* HAVE_IPV6 */
+	return FALSE;
+}
+
 static void
 resolve_address (SoupDNSCacheEntry *entry)
 {

Modified: trunk/libsoup/soup-dns.h
==============================================================================
--- trunk/libsoup/soup-dns.h	(original)
+++ trunk/libsoup/soup-dns.h	Wed Apr  9 03:02:02 2008
@@ -14,6 +14,7 @@
 
 void             soup_dns_init                 (void);
 char            *soup_dns_ntop                 (struct sockaddr *sa);
+gboolean         soup_dns_is_ip_address        (const char *name);
 
 typedef struct SoupDNSLookup SoupDNSLookup;
 

Modified: trunk/libsoup/soup-headers.c
==============================================================================
--- trunk/libsoup/soup-headers.c	(original)
+++ trunk/libsoup/soup-headers.c	Wed Apr  9 03:02:02 2008
@@ -367,23 +367,23 @@
 }
 
 static const char *
-skip_commas (const char *s)
+skip_delims (const char *s, char delim)
 {
-	/* The grammar allows for multiple commas */
-	while (g_ascii_isspace (*s) || *s == ',')
+	/* The grammar allows for multiple delimiters */
+	while (g_ascii_isspace (*s) || *s == delim)
 		s++;
 	return s;
 }
 
 static const char *
-skip_item (const char *s)
+skip_item (const char *s, char delim)
 {
 	gboolean quoted = FALSE;
 	const char *start = s;
 
 	/* A list item ends at the last non-whitespace character
-	 * before a comma which is not inside a quoted-string. Or at
-	 * the end of the string.
+	 * before a delimiter which is not inside a quoted-string. Or
+	 * at the end of the string.
 	 */
 
 	while (*s) {
@@ -393,7 +393,7 @@
 			if (*s == '\\' && *(s + 1))
 				s++;
 		} else {
-			if (*s == ',')
+			if (*s == delim)
 				break;
 		}
 		s++;
@@ -402,6 +402,22 @@
 	return unskip_lws (s, start);
 }
 
+static GSList *
+parse_list (const char *header, char delim)
+{
+	GSList *list = NULL;
+	const char *end;
+
+	header = skip_delims (header, delim);
+	while (*header) {
+		end = skip_item (header, delim);
+		list = g_slist_prepend (list, g_strndup (header, end - header));
+		header = skip_delims (end, delim);
+	}
+
+	return g_slist_reverse (list);
+}
+
 /**
  * soup_header_parse_list:
  * @header: a header value
@@ -415,17 +431,7 @@
 GSList *
 soup_header_parse_list (const char *header)
 {
-	GSList *list = NULL;
-	const char *end;
-
-	header = skip_commas (header);
-	while (*header) {
-		end = skip_item (header);
-		list = g_slist_prepend (list, g_strndup (header, end - header));
-		header = skip_commas (end);
-	}
-
-	return g_slist_reverse (list);
+	return parse_list (header, ',');
 }
 
 typedef struct {
@@ -569,13 +575,13 @@
 	const char *end;
 	guint len = strlen (token);
 
-	header = skip_commas (header);
+	header = skip_delims (header, ',');
 	while (*header) {
-		end = skip_item (header);
+		end = skip_item (header, ',');
 		if (end - header == len &&
 		    !g_ascii_strncasecmp (header, token, len))
 			return TRUE;
-		header = skip_commas (end);
+		header = skip_delims (end, ',');
 	}
 
 	return FALSE;
@@ -596,26 +602,14 @@
 	*dst = '\0';
 }
 
-/**
- * soup_header_parse_param_list:
- * @header: a header value
- *
- * Parses a header which is a list of something like
- *   token [ "=" ( token | quoted-string ) ]
- *
- * Tokens that don't have an associated value will still be added to
- * the resulting hash table, but with a %NULL value.
- * 
- * Return value: a #GHashTable of list elements.
- **/
-GHashTable *
-soup_header_parse_param_list (const char *header)
+static GHashTable *
+parse_param_list (const char *header, char delim)
 {
 	GHashTable *params;
 	GSList *list, *iter;
 	char *item, *eq, *name_end, *value;
 
-	list = soup_header_parse_list (header);
+	list = parse_list (header, delim);
 	if (!list)
 		return NULL;
 
@@ -651,6 +645,45 @@
 }
 
 /**
+ * soup_header_parse_param_list:
+ * @header: a header value
+ *
+ * Parses a header which is a comma-delimited list of something like
+ *
+ *   token [ "=" ( token | quoted-string ) ]
+ *
+ * Tokens that don't have an associated value will still be added to
+ * the resulting hash table, but with a %NULL value.
+ * 
+ * Return value: a #GHashTable of list elements.
+ **/
+GHashTable *
+soup_header_parse_param_list (const char *header)
+{
+	return parse_param_list (header, ',');
+}
+
+/**
+ * soup_header_parse_semi_param_list:
+ * @header: a header value
+ *
+ * Parses a header which is a semicolon-delimited list of something
+ * like
+ *
+ *   token [ "=" ( token | quoted-string ) ]
+ *
+ * Tokens that don't have an associated value will still be added to
+ * the resulting hash table, but with a %NULL value.
+ * 
+ * Return value: a #GHashTable of list elements.
+ **/
+GHashTable *
+soup_header_parse_semi_param_list (const char *header)
+{
+	return parse_param_list (header, ';');
+}
+
+/**
  * soup_header_free_param_list:
  * @param_list: a #GHashTable returned from soup_header_parse_param_list()
  *

Modified: trunk/libsoup/soup-headers.h
==============================================================================
--- trunk/libsoup/soup-headers.h	(original)
+++ trunk/libsoup/soup-headers.h	Wed Apr  9 03:02:02 2008
@@ -42,8 +42,9 @@
 gboolean    soup_header_contains            (const char       *header,
 					     const char       *token);
 
-GHashTable *soup_header_parse_param_list    (const char       *header);
-void        soup_header_free_param_list     (GHashTable       *param_list);
+GHashTable *soup_header_parse_param_list      (const char       *header);
+GHashTable *soup_header_parse_semi_param_list (const char       *header);
+void        soup_header_free_param_list       (GHashTable       *param_list);
 
 G_END_DECLS
 

Modified: trunk/libsoup/soup-types.h
==============================================================================
--- trunk/libsoup/soup-types.h	(original)
+++ trunk/libsoup/soup-types.h	Wed Apr  9 03:02:02 2008
@@ -16,6 +16,8 @@
 typedef struct _SoupAddress           SoupAddress;
 typedef struct _SoupAuth              SoupAuth;
 typedef struct _SoupAuthDomain        SoupAuthDomain;
+typedef struct _SoupCookie            SoupCookie;
+typedef struct _SoupDate              SoupDate;
 typedef struct _SoupMessage           SoupMessage;
 typedef struct _SoupServer            SoupServer;
 typedef struct _SoupSession           SoupSession;

Modified: trunk/libsoup/soup.h
==============================================================================
--- trunk/libsoup/soup.h	(original)
+++ trunk/libsoup/soup.h	Wed Apr  9 03:02:02 2008
@@ -15,6 +15,8 @@
 #include <libsoup/soup-auth-domain.h>
 #include <libsoup/soup-auth-domain-basic.h>
 #include <libsoup/soup-auth-domain-digest.h>
+#include <libsoup/soup-cookie.h>
+#include <libsoup/soup-cookie-jar.h>
 #include <libsoup/soup-date.h>
 #include <libsoup/soup-enum-types.h>
 #include <libsoup/soup-form.h>



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]