summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Winship <danw@src.gnome.org>2008-04-09 02:02:02 +0000
committerDan Winship <danw@src.gnome.org>2008-04-09 02:02:02 +0000
commit8e58c854675aa26eb0d33212822cac3223d0c8a9 (patch)
treeda24b9517473812da14ea61440a2c16f6bfe9aec
parente1c6bf7690fc035e0a0560ebc6f3030eee6b3a8d (diff)
downloadlibsoup-8e58c854675aa26eb0d33212822cac3223d0c8a9.tar.gz
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. svn path=/trunk/; revision=1135
-rw-r--r--ChangeLog24
-rw-r--r--libsoup/Makefile.am4
-rw-r--r--libsoup/soup-cookie-jar.c311
-rw-r--r--libsoup/soup-cookie-jar.h48
-rw-r--r--libsoup/soup-cookie.c856
-rw-r--r--libsoup/soup-cookie.h75
-rw-r--r--libsoup/soup-date.c18
-rw-r--r--libsoup/soup-date.h6
-rw-r--r--libsoup/soup-dns.c25
-rw-r--r--libsoup/soup-dns.h1
-rw-r--r--libsoup/soup-headers.c105
-rw-r--r--libsoup/soup-headers.h5
-rw-r--r--libsoup/soup-types.h2
-rw-r--r--libsoup/soup.h2
14 files changed, 1442 insertions, 40 deletions
diff --git a/ChangeLog b/ChangeLog
index 5ab21b4a..3ef59cb1 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,29 @@
2008-04-08 Dan Winship <danw@gnome.org>
+ 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.
+
+2008-04-08 Dan Winship <danw@gnome.org>
+
* libsoup/soup-auth-manager.c: Make this a GObject and
specifically a SoupSessionFeature. Add an "authenticate" signal,
and emit that rather than explicitly calling into the SoupSession
diff --git a/libsoup/Makefile.am b/libsoup/Makefile.am
index 1568c151..97f3bf6c 100644
--- a/libsoup/Makefile.am
+++ b/libsoup/Makefile.am
@@ -51,6 +51,8 @@ soup_headers = \
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 @@ libsoup_2_4_la_SOURCES = \
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 \
diff --git a/libsoup/soup-cookie-jar.c b/libsoup/soup-cookie-jar.c
new file mode 100644
index 00000000..2503f20a
--- /dev/null
+++ b/libsoup/soup-cookie-jar.c
@@ -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);
+}
+
diff --git a/libsoup/soup-cookie-jar.h b/libsoup/soup-cookie-jar.h
new file mode 100644
index 00000000..03e99f72
--- /dev/null
+++ b/libsoup/soup-cookie-jar.h
@@ -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 */
diff --git a/libsoup/soup-cookie.c b/libsoup/soup-cookie.c
new file mode 100644
index 00000000..f29ab885
--- /dev/null
+++ b/libsoup/soup-cookie.c
@@ -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;
+}
diff --git a/libsoup/soup-cookie.h b/libsoup/soup-cookie.h
new file mode 100644
index 00000000..84b46029
--- /dev/null
+++ b/libsoup/soup-cookie.h
@@ -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 */
diff --git a/libsoup/soup-date.c b/libsoup/soup-date.c
index 49a03507..4aa3c3b0 100644
--- a/libsoup/soup-date.c
+++ b/libsoup/soup-date.c
@@ -566,6 +566,24 @@ soup_date_to_time_t (SoupDate *date)
}
/**
+ * 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
*
diff --git a/libsoup/soup-date.h b/libsoup/soup-date.h
index a079e8e7..043cd0ab 100644
--- a/libsoup/soup-date.h
+++ b/libsoup/soup-date.h
@@ -12,7 +12,7 @@
G_BEGIN_DECLS
-typedef struct {
+struct _SoupDate {
int year;
int month;
int day;
@@ -23,7 +23,7 @@ typedef struct {
gboolean utc;
int offset;
-} SoupDate;
+};
typedef enum {
SOUP_DATE_HTTP = 1,
@@ -52,6 +52,8 @@ char *soup_date_to_string (SoupDate *date,
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);
diff --git a/libsoup/soup-dns.c b/libsoup/soup-dns.c
index 34fc399a..94c76e71 100644
--- a/libsoup/soup-dns.c
+++ b/libsoup/soup-dns.c
@@ -303,6 +303,31 @@ soup_dns_ntop (struct sockaddr *sa)
}
}
+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)
{
diff --git a/libsoup/soup-dns.h b/libsoup/soup-dns.h
index 6519a9c5..5e5584a2 100644
--- a/libsoup/soup-dns.h
+++ b/libsoup/soup-dns.h
@@ -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;
diff --git a/libsoup/soup-headers.c b/libsoup/soup-headers.c
index b1320033..662404ba 100644
--- a/libsoup/soup-headers.c
+++ b/libsoup/soup-headers.c
@@ -367,23 +367,23 @@ unskip_lws (const char *s, const char *start)
}
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 @@ skip_item (const char *s)
if (*s == '\\' && *(s + 1))
s++;
} else {
- if (*s == ',')
+ if (*s == delim)
break;
}
s++;
@@ -402,6 +402,22 @@ skip_item (const char *s)
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 @@ skip_item (const char *s)
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 @@ soup_header_contains (const char *header, const char *token)
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 @@ decode_quoted_string (char *quoted_string)
*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 (const char *header)
}
/**
+ * 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()
*
diff --git a/libsoup/soup-headers.h b/libsoup/soup-headers.h
index d040e755..80b56e3b 100644
--- a/libsoup/soup-headers.h
+++ b/libsoup/soup-headers.h
@@ -42,8 +42,9 @@ void soup_header_free_list (GSList *list);
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
diff --git a/libsoup/soup-types.h b/libsoup/soup-types.h
index 929506c8..5f44e37c 100644
--- a/libsoup/soup-types.h
+++ b/libsoup/soup-types.h
@@ -16,6 +16,8 @@ G_BEGIN_DECLS
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;
diff --git a/libsoup/soup.h b/libsoup/soup.h
index eeb254a3..f096f395 100644
--- a/libsoup/soup.h
+++ b/libsoup/soup.h
@@ -15,6 +15,8 @@ extern "C" {
#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>