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