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