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