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