1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
5 * Copyright (C) 2008 Red Hat, Inc.
15 #include "soup-cookie.h"
16 #include "soup-cookie-jar.h"
17 #include "soup-date.h"
18 #include "soup-message.h"
19 #include "soup-session-feature.h"
23 * SECTION:soup-cookie-jar
26 * A #SoupCookieJar stores #SoupCookie<!-- -->s and arrange for them
27 * to be sent with the appropriate #SoupMessage<!-- -->s.
28 * #SoupCookieJar implements #SoupSessionFeature, so you can add a
29 * cookie jar to a session with soup_session_add_feature() or
30 * soup_session_add_feature_by_type().
32 * Note that the base #SoupCookieJar class does not support any form
33 * of long-term cookie persistence.
36 static void soup_cookie_jar_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
37 static void request_queued (SoupSessionFeature *feature, SoupSession *session,
39 static void request_started (SoupSessionFeature *feature, SoupSession *session,
40 SoupMessage *msg, SoupSocket *socket);
41 static void request_unqueued (SoupSessionFeature *feature, SoupSession *session,
44 G_DEFINE_TYPE_WITH_CODE (SoupCookieJar, soup_cookie_jar, G_TYPE_OBJECT,
45 G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
46 soup_cookie_jar_session_feature_init))
50 } SoupCookieJarPrivate;
51 #define SOUP_COOKIE_JAR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_COOKIE_JAR, SoupCookieJarPrivate))
54 soup_cookie_jar_init (SoupCookieJar *jar)
56 SoupCookieJarPrivate *priv = SOUP_COOKIE_JAR_GET_PRIVATE (jar);
58 priv->domains = g_hash_table_new_full (g_str_hash, g_str_equal,
63 finalize (GObject *object)
65 SoupCookieJarPrivate *priv = SOUP_COOKIE_JAR_GET_PRIVATE (object);
69 g_hash_table_iter_init (&iter, priv->domains);
70 while (g_hash_table_iter_next (&iter, &key, &value))
71 soup_cookies_free (value);
72 g_hash_table_destroy (priv->domains);
74 G_OBJECT_CLASS (soup_cookie_jar_parent_class)->finalize (object);
78 soup_cookie_jar_class_init (SoupCookieJarClass *jar_class)
80 GObjectClass *object_class = G_OBJECT_CLASS (jar_class);
82 g_type_class_add_private (jar_class, sizeof (SoupCookieJarPrivate));
84 object_class->finalize = finalize;
88 soup_cookie_jar_session_feature_init (SoupSessionFeatureInterface *feature_interface,
89 gpointer interface_data)
91 feature_interface->request_queued = request_queued;
92 feature_interface->request_started = request_started;
93 feature_interface->request_unqueued = request_unqueued;
97 * soup_cookie_jar_new:
99 * Creates a new #SoupCookieJar.
101 * Returns: a new #SoupCookieJar
104 soup_cookie_jar_new (void)
106 return g_object_new (SOUP_TYPE_COOKIE_JAR, NULL);
110 * soup_cookie_jar_save:
111 * @jar: a SoupCookieJar
113 * Tells @jar to save the state of its (non-session) cookies to some
114 * sort of permanent storage.
117 soup_cookie_jar_save (SoupCookieJar *jar)
119 if (SOUP_COOKIE_JAR_GET_CLASS (jar)->save)
120 SOUP_COOKIE_JAR_GET_CLASS (jar)->save (jar);
124 * soup_cookie_jar_get_cookies:
125 * @jar: a #SoupCookieJar
127 * @for_http: whether or not the return value is being passed directly
128 * to an HTTP operation
130 * Retrieves (in Cookie-header form) the list of cookies that would
131 * be sent with a request to @uri.
133 * If @for_http is %TRUE, the return value will include cookies marked
134 * "HttpOnly" (that is, cookies that the server wishes to keep hidden
135 * from client-side scripting operations such as the JavaScript
136 * document.cookies property). Since #SoupCookieJar sets the Cookie
137 * header itself when making the actual HTTP request, you should
138 * almost certainly be setting @for_http to %FALSE if you are calling
141 * Return value: the cookies, in string form, or %NULL if there are no
145 soup_cookie_jar_get_cookies (SoupCookieJar *jar, SoupURI *uri,
148 SoupCookieJarPrivate *priv;
149 GSList *cookies, *domain_cookies;
150 char *domain, *cur, *next, *result;
152 g_return_val_if_fail (SOUP_IS_COOKIE_JAR (jar), NULL);
153 priv = SOUP_COOKIE_JAR_GET_PRIVATE (jar);
155 /* The logic here is a little weird, but the plan is that if
156 * uri->host is "www.foo.com", we will end up looking up
157 * cookies for ".www.foo.com", "www.foo.com", ".foo.com", and
158 * ".com", in that order. (Logic stolen from Mozilla.)
161 domain = cur = g_strdup_printf (".%s", uri->host);
164 domain_cookies = g_hash_table_lookup (priv->domains, cur);
165 while (domain_cookies) {
166 SoupCookie *cookie = domain_cookies->data;
168 if (soup_cookie_applies_to_uri (cookie, uri) &&
169 (for_http || !cookie->http_only))
170 cookies = g_slist_append (cookies, cookie);
171 domain_cookies = domain_cookies->next;
175 next = strchr (cur + 1, '.');
181 result = soup_cookies_to_cookie_header (cookies);
182 g_slist_free (cookies);
189 get_cookies_for_domain (SoupCookieJar *jar, const char *domain)
191 SoupCookieJarPrivate *priv = SOUP_COOKIE_JAR_GET_PRIVATE (jar);
192 GSList *cookies, *orig_cookies, *c;
195 cookies = g_hash_table_lookup (priv->domains, domain);
196 c = orig_cookies = cookies;
200 if (cookie->expires && soup_date_is_past (cookie->expires)) {
201 cookies = g_slist_remove (cookies, cookie);
202 soup_cookie_free (cookie);
206 if (cookies != orig_cookies)
207 g_hash_table_insert (priv->domains, g_strdup (domain), cookies);
212 set_cookie (SoupCookieJar *jar, SoupCookie *cookie)
214 SoupCookieJarPrivate *priv = SOUP_COOKIE_JAR_GET_PRIVATE (jar);
215 GSList *old_cookies, *oc, *prev = NULL;
216 SoupCookie *old_cookie;
218 old_cookies = get_cookies_for_domain (jar, cookie->domain);
219 for (oc = old_cookies; oc; oc = oc->next) {
220 old_cookie = oc->data;
221 if (!strcmp (cookie->name, old_cookie->name)) {
222 /* The new cookie is a replacement for an old
223 * cookie. It might be pre-expired, but we
224 * don't worry about that here;
225 * get_cookies_for_domain() will delete it
228 soup_cookie_free (old_cookie);
235 /* The new cookie is... a new cookie */
236 if (cookie->expires && soup_date_is_past (cookie->expires))
237 soup_cookie_free (cookie);
239 prev = g_slist_append (prev, cookie);
241 old_cookies = g_slist_append (NULL, cookie);
242 g_hash_table_insert (priv->domains, g_strdup (cookie->domain),
248 * soup_cookie_jar_set_cookie:
249 * @jar: a #SoupCookieJar
250 * @uri: the URI setting the cookie
251 * @cookie: the stringified cookie to set
253 * Adds @cookie to @jar, exactly as though it had appeared in a
254 * Set-Cookie header returned from a request to @uri.
257 soup_cookie_jar_set_cookie (SoupCookieJar *jar, SoupURI *uri,
260 SoupCookie *soup_cookie;
262 g_return_if_fail (SOUP_IS_COOKIE_JAR (jar));
263 g_return_if_fail (cookie != NULL);
265 soup_cookie = soup_cookie_parse (cookie, uri);
267 set_cookie (jar, soup_cookie);
268 /* set_cookie will steal or free soup_cookie */
273 process_set_cookie_header (SoupMessage *msg, gpointer user_data)
275 SoupCookieJar *jar = user_data;
276 GSList *new_cookies, *nc;
278 new_cookies = soup_cookies_from_response (msg);
279 for (nc = new_cookies; nc; nc = nc->next)
280 set_cookie (jar, nc->data);
281 g_slist_free (new_cookies);
285 request_queued (SoupSessionFeature *feature, SoupSession *session,
288 soup_message_add_header_handler (msg, "got-headers",
290 G_CALLBACK (process_set_cookie_header),
295 request_started (SoupSessionFeature *feature, SoupSession *session,
296 SoupMessage *msg, SoupSocket *socket)
298 SoupCookieJar *jar = SOUP_COOKIE_JAR (feature);
301 cookies = soup_cookie_jar_get_cookies (jar, soup_message_get_uri (msg), TRUE);
302 soup_message_headers_replace (msg->request_headers,
308 request_unqueued (SoupSessionFeature *feature, SoupSession *session,
311 g_signal_handlers_disconnect_by_func (msg, process_set_cookie_header, feature);