Upgrade to 2.62.2
[platform/upstream/libsoup.git] / libsoup / soup-cookie-jar.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * soup-cookie-jar.c
4  *
5  * Copyright (C) 2008 Red Hat, Inc.
6  */
7
8 #ifdef HAVE_CONFIG_H
9 #include <config.h>
10 #endif
11
12 #include <string.h>
13
14 #include "soup-cookie-jar.h"
15 #include "soup-misc-private.h"
16 #include "soup.h"
17
18 /**
19  * SECTION:soup-cookie-jar
20  * @short_description: Automatic cookie handling for SoupSession
21  *
22  * A #SoupCookieJar stores #SoupCookie<!-- -->s and arrange for them
23  * to be sent with the appropriate #SoupMessage<!-- -->s.
24  * #SoupCookieJar implements #SoupSessionFeature, so you can add a
25  * cookie jar to a session with soup_session_add_feature() or
26  * soup_session_add_feature_by_type().
27  *
28  * Note that the base #SoupCookieJar class does not support any form
29  * of long-term cookie persistence.
30  **/
31
32 enum {
33         CHANGED,
34         LAST_SIGNAL
35 };
36
37 static guint signals[LAST_SIGNAL] = { 0 };
38
39 enum {
40         PROP_0,
41
42         PROP_READ_ONLY,
43         PROP_ACCEPT_POLICY,
44
45         LAST_PROP
46 };
47
48 typedef struct {
49         gboolean constructed, read_only;
50         GHashTable *domains, *serials;
51         guint serial;
52         SoupCookieJarAcceptPolicy accept_policy;
53 } SoupCookieJarPrivate;
54
55 static void soup_cookie_jar_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
56
57 G_DEFINE_TYPE_WITH_CODE (SoupCookieJar, soup_cookie_jar, G_TYPE_OBJECT,
58                          G_ADD_PRIVATE (SoupCookieJar)
59                          G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
60                                                 soup_cookie_jar_session_feature_init))
61
62 static void
63 soup_cookie_jar_init (SoupCookieJar *jar)
64 {
65         SoupCookieJarPrivate *priv = soup_cookie_jar_get_instance_private (jar);
66
67         priv->domains = g_hash_table_new_full (soup_str_case_hash,
68                                                soup_str_case_equal,
69                                                g_free, NULL);
70         priv->serials = g_hash_table_new (NULL, NULL);
71         priv->accept_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
72 }
73
74 static void
75 soup_cookie_jar_constructed (GObject *object)
76 {
77         SoupCookieJarPrivate *priv =
78                 soup_cookie_jar_get_instance_private (SOUP_COOKIE_JAR (object));
79
80         priv->constructed = TRUE;
81 }
82
83 static void
84 soup_cookie_jar_finalize (GObject *object)
85 {
86         SoupCookieJarPrivate *priv =
87                 soup_cookie_jar_get_instance_private (SOUP_COOKIE_JAR (object));
88         GHashTableIter iter;
89         gpointer key, value;
90
91         g_hash_table_iter_init (&iter, priv->domains);
92         while (g_hash_table_iter_next (&iter, &key, &value))
93                 soup_cookies_free (value);
94         g_hash_table_destroy (priv->domains);
95         g_hash_table_destroy (priv->serials);
96
97         G_OBJECT_CLASS (soup_cookie_jar_parent_class)->finalize (object);
98 }
99
100 static void
101 soup_cookie_jar_set_property (GObject *object, guint prop_id,
102                               const GValue *value, GParamSpec *pspec)
103 {
104         SoupCookieJarPrivate *priv =
105                 soup_cookie_jar_get_instance_private (SOUP_COOKIE_JAR (object));
106
107         switch (prop_id) {
108         case PROP_READ_ONLY:
109                 priv->read_only = g_value_get_boolean (value);
110                 break;
111         case PROP_ACCEPT_POLICY:
112                 priv->accept_policy = g_value_get_enum (value);
113                 break;
114         default:
115                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
116                 break;
117         }
118 }
119
120 static void
121 soup_cookie_jar_get_property (GObject *object, guint prop_id,
122                               GValue *value, GParamSpec *pspec)
123 {
124         SoupCookieJarPrivate *priv =
125                 soup_cookie_jar_get_instance_private (SOUP_COOKIE_JAR (object));
126
127         switch (prop_id) {
128         case PROP_READ_ONLY:
129                 g_value_set_boolean (value, priv->read_only);
130                 break;
131         case PROP_ACCEPT_POLICY:
132                 g_value_set_enum (value, priv->accept_policy);
133                 break;
134         default:
135                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
136                 break;
137         }
138 }
139
140 static gboolean
141 soup_cookie_jar_real_is_persistent (SoupCookieJar *jar)
142 {
143         return FALSE;
144 }
145
146 static void
147 soup_cookie_jar_class_init (SoupCookieJarClass *jar_class)
148 {
149         GObjectClass *object_class = G_OBJECT_CLASS (jar_class);
150
151         object_class->constructed = soup_cookie_jar_constructed;
152         object_class->finalize = soup_cookie_jar_finalize;
153         object_class->set_property = soup_cookie_jar_set_property;
154         object_class->get_property = soup_cookie_jar_get_property;
155
156         jar_class->is_persistent = soup_cookie_jar_real_is_persistent;
157
158         /**
159          * SoupCookieJar::changed:
160          * @jar: the #SoupCookieJar
161          * @old_cookie: the old #SoupCookie value
162          * @new_cookie: the new #SoupCookie value
163          *
164          * Emitted when @jar changes. If a cookie has been added,
165          * @new_cookie will contain the newly-added cookie and
166          * @old_cookie will be %NULL. If a cookie has been deleted,
167          * @old_cookie will contain the to-be-deleted cookie and
168          * @new_cookie will be %NULL. If a cookie has been changed,
169          * @old_cookie will contain its old value, and @new_cookie its
170          * new value.
171          **/
172         signals[CHANGED] =
173                 g_signal_new ("changed",
174                               G_OBJECT_CLASS_TYPE (object_class),
175                               G_SIGNAL_RUN_FIRST,
176                               G_STRUCT_OFFSET (SoupCookieJarClass, changed),
177                               NULL, NULL,
178                               NULL,
179                               G_TYPE_NONE, 2, 
180                               SOUP_TYPE_COOKIE | G_SIGNAL_TYPE_STATIC_SCOPE,
181                               SOUP_TYPE_COOKIE | G_SIGNAL_TYPE_STATIC_SCOPE);
182
183         /**
184          * SOUP_COOKIE_JAR_READ_ONLY:
185          *
186          * Alias for the #SoupCookieJar:read-only property. (Whether
187          * or not the cookie jar is read-only.)
188          **/
189         g_object_class_install_property (
190                 object_class, PROP_READ_ONLY,
191                 g_param_spec_boolean (SOUP_COOKIE_JAR_READ_ONLY,
192                                       "Read-only",
193                                       "Whether or not the cookie jar is read-only",
194                                       FALSE,
195                                       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
196
197         /**
198          * SOUP_COOKIE_JAR_ACCEPT_POLICY:
199          *
200          * Alias for the #SoupCookieJar:accept-policy property.
201          *
202          * Since: 2.30
203          */
204         /**
205          * SoupCookieJar:accept-policy:
206          *
207          * The policy the jar should follow to accept or reject cookies
208          *
209          * Since: 2.30
210          */
211         g_object_class_install_property (
212                 object_class, PROP_ACCEPT_POLICY,
213                 g_param_spec_enum (SOUP_COOKIE_JAR_ACCEPT_POLICY,
214                                    "Accept-policy",
215                                    "The policy the jar should follow to accept or reject cookies",
216                                    SOUP_TYPE_COOKIE_JAR_ACCEPT_POLICY,
217                                    SOUP_COOKIE_JAR_ACCEPT_ALWAYS,
218                                    G_PARAM_READWRITE));
219 }
220
221 /**
222  * soup_cookie_jar_new:
223  *
224  * Creates a new #SoupCookieJar. The base #SoupCookieJar class does
225  * not support persistent storage of cookies; use a subclass for that.
226  *
227  * Returns: a new #SoupCookieJar
228  *
229  * Since: 2.24
230  **/
231 SoupCookieJar *
232 soup_cookie_jar_new (void) 
233 {
234         return g_object_new (SOUP_TYPE_COOKIE_JAR, NULL);
235 }
236
237 /**
238  * soup_cookie_jar_save:
239  * @jar: a #SoupCookieJar
240  *
241  * This function exists for backward compatibility, but does not do
242  * anything any more; cookie jars are saved automatically when they
243  * are changed.
244  *
245  * Since: 2.24
246  *
247  * Deprecated: This is a no-op.
248  */
249 void
250 soup_cookie_jar_save (SoupCookieJar *jar)
251 {
252         /* Does nothing, obsolete */
253 }
254
255 static void
256 soup_cookie_jar_changed (SoupCookieJar *jar,
257                          SoupCookie *old, SoupCookie *new)
258 {
259         SoupCookieJarPrivate *priv = soup_cookie_jar_get_instance_private (jar);
260
261         if (old && old != new)
262                 g_hash_table_remove (priv->serials, old);
263         if (new) {
264                 priv->serial++;
265                 g_hash_table_insert (priv->serials, new, GUINT_TO_POINTER (priv->serial));
266         }
267
268         if (priv->read_only || !priv->constructed)
269                 return;
270
271         g_signal_emit (jar, signals[CHANGED], 0, old, new);
272 }
273
274 static int
275 compare_cookies (gconstpointer a, gconstpointer b, gpointer jar)
276 {
277         SoupCookie *ca = (SoupCookie *)a;
278         SoupCookie *cb = (SoupCookie *)b;
279         SoupCookieJarPrivate *priv = soup_cookie_jar_get_instance_private (jar);
280         int alen, blen;
281         guint aserial, bserial;
282
283         /* "Cookies with longer path fields are listed before cookies
284          * with shorter path field."
285          */
286         alen = ca->path ? strlen (ca->path) : 0;
287         blen = cb->path ? strlen (cb->path) : 0;
288         if (alen != blen)
289                 return blen - alen;
290
291         /* "Among cookies that have equal length path fields, cookies
292          * with earlier creation dates are listed before cookies with
293          * later creation dates."
294          */
295         aserial = GPOINTER_TO_UINT (g_hash_table_lookup (priv->serials, ca));
296         bserial = GPOINTER_TO_UINT (g_hash_table_lookup (priv->serials, cb));
297         return aserial - bserial;
298 }
299
300 static GSList *
301 get_cookies (SoupCookieJar *jar, SoupURI *uri, gboolean for_http, gboolean copy_cookies)
302 {
303         SoupCookieJarPrivate *priv;
304         GSList *cookies, *domain_cookies;
305         char *domain, *cur, *next_domain;
306         GSList *new_head, *cookies_to_remove = NULL, *p;
307
308         priv = soup_cookie_jar_get_instance_private (jar);
309
310         if (!uri->host)
311                 return NULL;
312
313         /* The logic here is a little weird, but the plan is that if
314          * uri->host is "www.foo.com", we will end up looking up
315          * cookies for ".www.foo.com", "www.foo.com", ".foo.com", and
316          * ".com", in that order. (Logic stolen from Mozilla.)
317          */
318         cookies = NULL;
319         domain = cur = g_strdup_printf (".%s", uri->host);
320         next_domain = domain + 1;
321         do {
322                 new_head = domain_cookies = g_hash_table_lookup (priv->domains, cur);
323                 while (domain_cookies) {
324                         GSList *next = domain_cookies->next;
325                         SoupCookie *cookie = domain_cookies->data;
326
327                         if (cookie->expires && soup_date_is_past (cookie->expires)) {
328                                 cookies_to_remove = g_slist_append (cookies_to_remove,
329                                                                     cookie);
330                                 new_head = g_slist_delete_link (new_head, domain_cookies);
331                                 g_hash_table_insert (priv->domains,
332                                                      g_strdup (cur),
333                                                      new_head);
334                         } else if (soup_cookie_applies_to_uri (cookie, uri) &&
335                                    (for_http || !cookie->http_only))
336                                 cookies = g_slist_append (cookies, copy_cookies ? soup_cookie_copy (cookie) : cookie);
337
338                         domain_cookies = next;
339                 }
340                 cur = next_domain;
341                 if (cur)
342                         next_domain = strchr (cur + 1, '.');
343         } while (cur);
344         g_free (domain);
345
346         for (p = cookies_to_remove; p; p = p->next) {
347                 SoupCookie *cookie = p->data;
348
349                 soup_cookie_jar_changed (jar, cookie, NULL);
350                 soup_cookie_free (cookie);
351         }
352         g_slist_free (cookies_to_remove);
353
354         return g_slist_sort_with_data (cookies, compare_cookies, jar);
355 }
356
357 /**
358  * soup_cookie_jar_get_cookies:
359  * @jar: a #SoupCookieJar
360  * @uri: a #SoupURI
361  * @for_http: whether or not the return value is being passed directly
362  * to an HTTP operation
363  *
364  * Retrieves (in Cookie-header form) the list of cookies that would
365  * be sent with a request to @uri.
366  *
367  * If @for_http is %TRUE, the return value will include cookies marked
368  * "HttpOnly" (that is, cookies that the server wishes to keep hidden
369  * from client-side scripting operations such as the JavaScript
370  * document.cookies property). Since #SoupCookieJar sets the Cookie
371  * header itself when making the actual HTTP request, you should
372  * almost certainly be setting @for_http to %FALSE if you are calling
373  * this.
374  *
375  * Return value: (nullable): the cookies, in string form, or %NULL if
376  * there are no cookies for @uri.
377  *
378  * Since: 2.24
379  **/
380 char *
381 soup_cookie_jar_get_cookies (SoupCookieJar *jar, SoupURI *uri,
382                              gboolean for_http)
383 {
384         GSList *cookies;
385
386         g_return_val_if_fail (SOUP_IS_COOKIE_JAR (jar), NULL);
387         g_return_val_if_fail (uri != NULL, NULL);
388
389         cookies = get_cookies (jar, uri, for_http, FALSE);
390
391         if (cookies) {
392                 char *result = soup_cookies_to_cookie_header (cookies);
393                 g_slist_free (cookies);
394
395                 if (!*result) {
396                         g_free (result);
397                         result = NULL;
398                 }
399                 return result;
400         } else
401                 return NULL;
402 }
403
404 /**
405  * soup_cookie_jar_get_cookie_list:
406  * @jar: a #SoupCookieJar
407  * @uri: a #SoupURI
408  * @for_http: whether or not the return value is being passed directly
409  * to an HTTP operation
410  *
411  * Retrieves the list of cookies that would be sent with a request to @uri
412  * as a #GSList of #SoupCookie objects.
413  *
414  * If @for_http is %TRUE, the return value will include cookies marked
415  * "HttpOnly" (that is, cookies that the server wishes to keep hidden
416  * from client-side scripting operations such as the JavaScript
417  * document.cookies property). Since #SoupCookieJar sets the Cookie
418  * header itself when making the actual HTTP request, you should
419  * almost certainly be setting @for_http to %FALSE if you are calling
420  * this.
421  *
422  * Return value: (transfer full) (element-type Soup.Cookie): a #GSList
423  * with the cookies in the @jar that would be sent with a request to @uri.
424  *
425  * Since: 2.40
426  **/
427 GSList *
428 soup_cookie_jar_get_cookie_list (SoupCookieJar *jar, SoupURI *uri, gboolean for_http)
429 {
430         g_return_val_if_fail (SOUP_IS_COOKIE_JAR (jar), NULL);
431         g_return_val_if_fail (uri != NULL, NULL);
432
433         return get_cookies (jar, uri, for_http, TRUE);
434 }
435
436 /**
437  * soup_cookie_jar_add_cookie:
438  * @jar: a #SoupCookieJar
439  * @cookie: (transfer full): a #SoupCookie
440  *
441  * Adds @cookie to @jar, emitting the 'changed' signal if we are modifying
442  * an existing cookie or adding a valid new cookie ('valid' means
443  * that the cookie's expire date is not in the past).
444  *
445  * @cookie will be 'stolen' by the jar, so don't free it afterwards.
446  *
447  * Since: 2.26
448  **/
449 void
450 soup_cookie_jar_add_cookie (SoupCookieJar *jar, SoupCookie *cookie)
451 {
452         SoupCookieJarPrivate *priv;
453         GSList *old_cookies, *oc, *last = NULL;
454         SoupCookie *old_cookie;
455
456         g_return_if_fail (SOUP_IS_COOKIE_JAR (jar));
457         g_return_if_fail (cookie != NULL);
458
459         /* Never accept cookies for public domains. */
460         if (!g_hostname_is_ip_address (cookie->domain) &&
461             soup_tld_domain_is_public_suffix (cookie->domain)) {
462                 soup_cookie_free (cookie);
463                 return;
464         }
465
466         priv = soup_cookie_jar_get_instance_private (jar);
467         old_cookies = g_hash_table_lookup (priv->domains, cookie->domain);
468         for (oc = old_cookies; oc; oc = oc->next) {
469                 old_cookie = oc->data;
470                 if (!strcmp (cookie->name, old_cookie->name) &&
471                     !g_strcmp0 (cookie->path, old_cookie->path)) {
472                         if (cookie->expires && soup_date_is_past (cookie->expires)) {
473                                 /* The new cookie has an expired date,
474                                  * this is the way the the server has
475                                  * of telling us that we have to
476                                  * remove the cookie.
477                                  */
478                                 old_cookies = g_slist_delete_link (old_cookies, oc);
479                                 g_hash_table_insert (priv->domains,
480                                                      g_strdup (cookie->domain),
481                                                      old_cookies);
482                                 soup_cookie_jar_changed (jar, old_cookie, NULL);
483                                 soup_cookie_free (old_cookie);
484                                 soup_cookie_free (cookie);
485                         } else {
486                                 oc->data = cookie;
487                                 soup_cookie_jar_changed (jar, old_cookie, cookie);
488                                 soup_cookie_free (old_cookie);
489                         }
490
491                         return;
492                 }
493                 last = oc;
494         }
495
496         /* The new cookie is... a new cookie */
497         if (cookie->expires && soup_date_is_past (cookie->expires)) {
498                 soup_cookie_free (cookie);
499                 return;
500         }
501
502         if (last)
503                 last->next = g_slist_append (NULL, cookie);
504         else {
505                 old_cookies = g_slist_append (NULL, cookie);
506                 g_hash_table_insert (priv->domains, g_strdup (cookie->domain),
507                                      old_cookies);
508         }
509
510         soup_cookie_jar_changed (jar, NULL, cookie);
511 }
512
513 static const char *
514 normalize_cookie_domain (const char *domain)
515 {
516         /* Trim any leading dot if present to transform the cookie
517          * domain into a valid hostname.
518          */
519         if (domain != NULL && domain[0] == '.')
520                 return domain + 1;
521         return domain;
522 }
523
524 static gboolean
525 incoming_cookie_is_third_party (SoupCookie *cookie, SoupURI *first_party)
526 {
527         const char *normalized_cookie_domain;
528         const char *cookie_base_domain;
529         const char *first_party_base_domain;
530
531         if (first_party == NULL || first_party->host == NULL)
532                 return TRUE;
533
534         normalized_cookie_domain = normalize_cookie_domain (cookie->domain);
535         cookie_base_domain = soup_tld_get_base_domain (normalized_cookie_domain, NULL);
536         if (cookie_base_domain == NULL)
537                 cookie_base_domain = cookie->domain;
538
539         first_party_base_domain = soup_tld_get_base_domain (first_party->host, NULL);
540         if (first_party_base_domain == NULL)
541                 first_party_base_domain = first_party->host;
542         return !soup_host_matches_host (cookie_base_domain, first_party_base_domain);
543 }
544
545 /**
546  * soup_cookie_jar_add_cookie_with_first_party:
547  * @jar: a #SoupCookieJar
548  * @first_party: the URI for the main document
549  * @cookie: (transfer full): a #SoupCookie
550  *
551  * Adds @cookie to @jar, emitting the 'changed' signal if we are modifying
552  * an existing cookie or adding a valid new cookie ('valid' means
553  * that the cookie's expire date is not in the past).
554  *
555  * @first_party will be used to reject cookies coming from third party
556  * resources in case such a security policy is set in the @jar.
557  *
558  * @cookie will be 'stolen' by the jar, so don't free it afterwards.
559  *
560  * Since: 2.40
561  **/
562 void
563 soup_cookie_jar_add_cookie_with_first_party (SoupCookieJar *jar, SoupURI *first_party, SoupCookie *cookie)
564 {
565         SoupCookieJarPrivate *priv;
566
567         g_return_if_fail (SOUP_IS_COOKIE_JAR (jar));
568         g_return_if_fail (first_party != NULL);
569         g_return_if_fail (cookie != NULL);
570
571         priv = soup_cookie_jar_get_instance_private (jar);
572         if (priv->accept_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER) {
573                 soup_cookie_free (cookie);
574                 return;
575         }
576
577         if (priv->accept_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS ||
578             !incoming_cookie_is_third_party (cookie, first_party)) {
579                 /* will steal or free soup_cookie */
580                 soup_cookie_jar_add_cookie (jar, cookie);
581         } else {
582                 soup_cookie_free (cookie);
583         }
584 }
585
586 /**
587  * soup_cookie_jar_set_cookie:
588  * @jar: a #SoupCookieJar
589  * @uri: the URI setting the cookie
590  * @cookie: the stringified cookie to set
591  *
592  * Adds @cookie to @jar, exactly as though it had appeared in a
593  * Set-Cookie header returned from a request to @uri.
594  *
595  * Keep in mind that if the #SoupCookieJarAcceptPolicy
596  * %SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY is set you'll need to use
597  * soup_cookie_jar_set_cookie_with_first_party(), otherwise the jar
598  * will have no way of knowing if the cookie is being set by a third
599  * party or not.
600  *
601  * Since: 2.24
602  **/
603 void
604 soup_cookie_jar_set_cookie (SoupCookieJar *jar, SoupURI *uri,
605                             const char *cookie)
606 {
607         SoupCookie *soup_cookie;
608         SoupCookieJarPrivate *priv;
609
610         g_return_if_fail (SOUP_IS_COOKIE_JAR (jar));
611         g_return_if_fail (uri != NULL);
612         g_return_if_fail (cookie != NULL);
613
614         if (!uri->host)
615                 return;
616
617         priv = soup_cookie_jar_get_instance_private (jar);
618         if (priv->accept_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
619                 return;
620
621         g_return_if_fail (priv->accept_policy != SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY);
622
623         soup_cookie = soup_cookie_parse (cookie, uri);
624         if (soup_cookie) {
625                 /* will steal or free soup_cookie */
626                 soup_cookie_jar_add_cookie (jar, soup_cookie);
627         }
628 }
629
630 /**
631  * soup_cookie_jar_set_cookie_with_first_party:
632  * @jar: a #SoupCookieJar
633  * @uri: the URI setting the cookie
634  * @first_party: the URI for the main document
635  * @cookie: the stringified cookie to set
636  *
637  * Adds @cookie to @jar, exactly as though it had appeared in a
638  * Set-Cookie header returned from a request to @uri. @first_party
639  * will be used to reject cookies coming from third party resources in
640  * case such a security policy is set in the @jar.
641  *
642  * Since: 2.30
643  **/
644 void
645 soup_cookie_jar_set_cookie_with_first_party (SoupCookieJar *jar,
646                                              SoupURI *uri,
647                                              SoupURI *first_party,
648                                              const char *cookie)
649 {
650         SoupCookie *soup_cookie;
651
652         g_return_if_fail (SOUP_IS_COOKIE_JAR (jar));
653         g_return_if_fail (uri != NULL);
654         g_return_if_fail (first_party != NULL);
655         g_return_if_fail (cookie != NULL);
656
657         if (!uri->host)
658                 return;
659
660         soup_cookie = soup_cookie_parse (cookie, uri);
661         if (soup_cookie)
662                 soup_cookie_jar_add_cookie_with_first_party (jar, first_party, soup_cookie);
663 }
664
665 static void
666 process_set_cookie_header (SoupMessage *msg, gpointer user_data)
667 {
668         SoupCookieJar *jar = user_data;
669         SoupCookieJarPrivate *priv = soup_cookie_jar_get_instance_private (jar);
670         GSList *new_cookies, *nc;
671
672         if (priv->accept_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
673                 return;
674
675         new_cookies = soup_cookies_from_response (msg);
676         for (nc = new_cookies; nc; nc = nc->next) {
677                 SoupURI *first_party = soup_message_get_first_party (msg);
678                 
679                 if ((priv->accept_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY &&
680                      !incoming_cookie_is_third_party (nc->data, first_party)) ||
681                     priv->accept_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
682                         soup_cookie_jar_add_cookie (jar, nc->data);
683                 else
684                         soup_cookie_free (nc->data);
685         }
686         g_slist_free (new_cookies);
687 }
688
689 static void
690 msg_starting_cb (SoupMessage *msg, gpointer feature)
691 {
692         SoupCookieJar *jar = SOUP_COOKIE_JAR (feature);
693         char *cookies;
694
695         cookies = soup_cookie_jar_get_cookies (jar, soup_message_get_uri (msg), TRUE);
696         if (cookies) {
697                 soup_message_headers_replace (msg->request_headers,
698                                               "Cookie", cookies);
699                 g_free (cookies);
700         } else
701                 soup_message_headers_remove (msg->request_headers, "Cookie");
702 }
703
704 static void
705 soup_cookie_jar_request_queued (SoupSessionFeature *feature,
706                                 SoupSession *session,
707                                 SoupMessage *msg)
708 {
709         g_signal_connect (msg, "starting",
710                           G_CALLBACK (msg_starting_cb),
711                           feature);
712
713         soup_message_add_header_handler (msg, "got-headers",
714                                          "Set-Cookie",
715                                          G_CALLBACK (process_set_cookie_header),
716                                          feature);
717 }
718
719 static void
720 soup_cookie_jar_request_unqueued (SoupSessionFeature *feature,
721                                   SoupSession *session,
722                                   SoupMessage *msg)
723 {
724         g_signal_handlers_disconnect_by_func (msg, process_set_cookie_header, feature);
725 }
726
727 static void
728 soup_cookie_jar_session_feature_init (SoupSessionFeatureInterface *feature_interface,
729                                       gpointer interface_data)
730 {
731         feature_interface->request_queued = soup_cookie_jar_request_queued;
732         feature_interface->request_unqueued = soup_cookie_jar_request_unqueued;
733 }
734
735 /**
736  * soup_cookie_jar_all_cookies:
737  * @jar: a #SoupCookieJar
738  *
739  * Constructs a #GSList with every cookie inside the @jar.
740  * The cookies in the list are a copy of the original, so
741  * you have to free them when you are done with them.
742  *
743  * Return value: (transfer full) (element-type Soup.Cookie): a #GSList
744  * with all the cookies in the @jar.
745  *
746  * Since: 2.26
747  **/
748 GSList *
749 soup_cookie_jar_all_cookies (SoupCookieJar *jar)
750 {
751         SoupCookieJarPrivate *priv;
752         GHashTableIter iter;
753         GSList *l = NULL;
754         gpointer key, value;
755
756         g_return_val_if_fail (SOUP_IS_COOKIE_JAR (jar), NULL);
757
758         priv = soup_cookie_jar_get_instance_private (jar);
759
760         g_hash_table_iter_init (&iter, priv->domains);
761
762         while (g_hash_table_iter_next (&iter, &key, &value)) {
763                 GSList *p, *cookies = value;
764                 for (p = cookies; p; p = p->next)
765                         l = g_slist_prepend (l, soup_cookie_copy (p->data));
766         }
767
768         return l;
769 }
770
771 /**
772  * soup_cookie_jar_delete_cookie:
773  * @jar: a #SoupCookieJar
774  * @cookie: a #SoupCookie
775  *
776  * Deletes @cookie from @jar, emitting the 'changed' signal.
777  *
778  * Since: 2.26
779  **/
780 void
781 soup_cookie_jar_delete_cookie (SoupCookieJar *jar,
782                                SoupCookie    *cookie)
783 {
784         SoupCookieJarPrivate *priv;
785         GSList *cookies, *p;
786
787         g_return_if_fail (SOUP_IS_COOKIE_JAR (jar));
788         g_return_if_fail (cookie != NULL);
789
790         priv = soup_cookie_jar_get_instance_private (jar);
791
792         cookies = g_hash_table_lookup (priv->domains, cookie->domain);
793         if (cookies == NULL)
794                 return;
795
796         for (p = cookies; p; p = p->next ) {
797                 SoupCookie *c = (SoupCookie*)p->data;
798                 if (soup_cookie_equal (cookie, c)) {
799                         cookies = g_slist_delete_link (cookies, p);
800                         g_hash_table_insert (priv->domains,
801                                              g_strdup (cookie->domain),
802                                              cookies);
803                         soup_cookie_jar_changed (jar, c, NULL);
804                         soup_cookie_free (c);
805                         return;
806                 }
807         }
808 }
809
810 /**
811  * SoupCookieJarAcceptPolicy:
812  * @SOUP_COOKIE_JAR_ACCEPT_ALWAYS: accept all cookies unconditionally.
813  * @SOUP_COOKIE_JAR_ACCEPT_NEVER: reject all cookies unconditionally.
814  * @SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY: accept all cookies set by
815  * the main document loaded in the application using libsoup. An
816  * example of the most common case, web browsers, would be: If
817  * http://www.example.com is the page loaded, accept all cookies set
818  * by example.com, but if a resource from http://www.third-party.com
819  * is loaded from that page reject any cookie that it could try to
820  * set. For libsoup to be able to tell apart first party cookies from
821  * the rest, the application must call soup_message_set_first_party()
822  * on each outgoing #SoupMessage, setting the #SoupURI of the main
823  * document. If no first party is set in a message when this policy is
824  * in effect, cookies will be assumed to be third party by default.
825  *
826  * The policy for accepting or rejecting cookies returned in
827  * responses.
828  *
829  * Since: 2.30
830  */
831
832 /**
833  * soup_cookie_jar_get_accept_policy:
834  * @jar: a #SoupCookieJar
835  *
836  * Gets @jar's #SoupCookieJarAcceptPolicy
837  *
838  * Returns: the #SoupCookieJarAcceptPolicy set in the @jar
839  *
840  * Since: 2.30
841  **/
842 SoupCookieJarAcceptPolicy
843 soup_cookie_jar_get_accept_policy (SoupCookieJar *jar)
844 {
845         SoupCookieJarPrivate *priv;
846
847         g_return_val_if_fail (SOUP_IS_COOKIE_JAR (jar), SOUP_COOKIE_JAR_ACCEPT_ALWAYS);
848
849         priv = soup_cookie_jar_get_instance_private (jar);
850         return priv->accept_policy;
851 }
852
853 /**
854  * soup_cookie_jar_set_accept_policy:
855  * @jar: a #SoupCookieJar
856  * @policy: a #SoupCookieJarAcceptPolicy
857  * 
858  * Sets @policy as the cookie acceptance policy for @jar.
859  *
860  * Since: 2.30
861  **/
862 void
863 soup_cookie_jar_set_accept_policy (SoupCookieJar *jar,
864                                    SoupCookieJarAcceptPolicy policy)
865 {
866         SoupCookieJarPrivate *priv;
867
868         g_return_if_fail (SOUP_IS_COOKIE_JAR (jar));
869
870         priv = soup_cookie_jar_get_instance_private (jar);
871
872         if (priv->accept_policy != policy) {
873                 priv->accept_policy = policy;
874                 g_object_notify (G_OBJECT (jar), SOUP_COOKIE_JAR_ACCEPT_POLICY);
875         }
876 }
877
878 /**
879  * soup_cookie_jar_is_persistent:
880  * @jar: a #SoupCookieJar
881  *
882  * Gets whether @jar stores cookies persistenly.
883  *
884  * Returns: %TRUE if @jar storage is persistent or %FALSE otherwise.
885  *
886  * Since: 2.40
887  **/
888 gboolean
889 soup_cookie_jar_is_persistent (SoupCookieJar *jar)
890 {
891         g_return_val_if_fail (SOUP_IS_COOKIE_JAR (jar), FALSE);
892
893         return SOUP_COOKIE_JAR_GET_CLASS (jar)->is_persistent (jar);
894 }