diff options
author | Dan Winship <danw@src.gnome.org> | 2008-04-09 02:02:02 +0000 |
---|---|---|
committer | Dan Winship <danw@src.gnome.org> | 2008-04-09 02:02:02 +0000 |
commit | 8e58c854675aa26eb0d33212822cac3223d0c8a9 (patch) | |
tree | da24b9517473812da14ea61440a2c16f6bfe9aec | |
parent | e1c6bf7690fc035e0a0560ebc6f3030eee6b3a8d (diff) | |
download | libsoup-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-- | ChangeLog | 24 | ||||
-rw-r--r-- | libsoup/Makefile.am | 4 | ||||
-rw-r--r-- | libsoup/soup-cookie-jar.c | 311 | ||||
-rw-r--r-- | libsoup/soup-cookie-jar.h | 48 | ||||
-rw-r--r-- | libsoup/soup-cookie.c | 856 | ||||
-rw-r--r-- | libsoup/soup-cookie.h | 75 | ||||
-rw-r--r-- | libsoup/soup-date.c | 18 | ||||
-rw-r--r-- | libsoup/soup-date.h | 6 | ||||
-rw-r--r-- | libsoup/soup-dns.c | 25 | ||||
-rw-r--r-- | libsoup/soup-dns.h | 1 | ||||
-rw-r--r-- | libsoup/soup-headers.c | 105 | ||||
-rw-r--r-- | libsoup/soup-headers.h | 5 | ||||
-rw-r--r-- | libsoup/soup-types.h | 2 | ||||
-rw-r--r-- | libsoup/soup.h | 2 |
14 files changed, 1442 insertions, 40 deletions
@@ -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> |