Check that the cookie was parsed successfully before setting it
[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-message.h"
19 #include "soup-session-feature.h"
20 #include "soup-uri.h"
21
22 /**
23  * SECTION:soup-cookie-jar
24  * @short_description: 
25  *
26  * A #SoupCookieJar stores #SoupCookie<!-- -->s and arrange for them
27  * to be sent with the appropriate #SoupMessage<!-- -->s.
28  * #SoupCookieJar implements #SoupSessionFeature, so you can add a
29  * cookie jar to a session with soup_session_add_feature() or
30  * soup_session_add_feature_by_type().
31  *
32  * Note that the base #SoupCookieJar class does not support any form
33  * of long-term cookie persistence.
34  **/
35
36 static void soup_cookie_jar_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
37 static void request_queued (SoupSessionFeature *feature, SoupSession *session,
38                             SoupMessage *msg);
39 static void request_started (SoupSessionFeature *feature, SoupSession *session,
40                              SoupMessage *msg, SoupSocket *socket);
41 static void request_unqueued (SoupSessionFeature *feature, SoupSession *session,
42                               SoupMessage *msg);
43
44 G_DEFINE_TYPE_WITH_CODE (SoupCookieJar, soup_cookie_jar, G_TYPE_OBJECT,
45                          G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
46                                                 soup_cookie_jar_session_feature_init));
47
48 typedef struct {
49         GHashTable *domains;
50 } SoupCookieJarPrivate;
51 #define SOUP_COOKIE_JAR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_COOKIE_JAR, SoupCookieJarPrivate))
52
53 static void
54 soup_cookie_jar_init (SoupCookieJar *jar)
55 {
56         SoupCookieJarPrivate *priv = SOUP_COOKIE_JAR_GET_PRIVATE (jar);
57
58         priv->domains = g_hash_table_new_full (g_str_hash, g_str_equal,
59                                                g_free, NULL);
60 }
61
62 static void
63 finalize (GObject *object)
64 {
65         SoupCookieJarPrivate *priv = SOUP_COOKIE_JAR_GET_PRIVATE (object);
66         GHashTableIter iter;
67         gpointer key, value;
68
69         g_hash_table_iter_init (&iter, priv->domains);
70         while (g_hash_table_iter_next (&iter, &key, &value))
71                 soup_cookies_free (value);
72         g_hash_table_destroy (priv->domains);
73
74         G_OBJECT_CLASS (soup_cookie_jar_parent_class)->finalize (object);
75 }
76
77 static void
78 soup_cookie_jar_class_init (SoupCookieJarClass *jar_class)
79 {
80         GObjectClass *object_class = G_OBJECT_CLASS (jar_class);
81
82         g_type_class_add_private (jar_class, sizeof (SoupCookieJarPrivate));
83
84         object_class->finalize = finalize;
85 }
86
87 static void
88 soup_cookie_jar_session_feature_init (SoupSessionFeatureInterface *feature_interface,
89                                       gpointer interface_data)
90 {
91         feature_interface->request_queued = request_queued;
92         feature_interface->request_started = request_started;
93         feature_interface->request_unqueued = request_unqueued;
94 }
95
96 /**
97  * soup_cookie_jar_new:
98  *
99  * Creates a new #SoupCookieJar.
100  *
101  * Returns: a new #SoupCookieJar
102  **/
103 SoupCookieJar *
104 soup_cookie_jar_new (void) 
105 {
106         return g_object_new (SOUP_TYPE_COOKIE_JAR, NULL);
107 }
108
109 /**
110  * soup_cookie_jar_save:
111  * @jar: a SoupCookieJar
112  *
113  * Tells @jar to save the state of its (non-session) cookies to some
114  * sort of permanent storage.
115  **/
116 void
117 soup_cookie_jar_save (SoupCookieJar *jar)
118 {
119         if (SOUP_COOKIE_JAR_GET_CLASS (jar)->save)
120                 SOUP_COOKIE_JAR_GET_CLASS (jar)->save (jar);
121 }
122
123 /**
124  * soup_cookie_jar_get_cookies:
125  * @jar: a #SoupCookieJar
126  * @uri: a #SoupURI
127  * @for_http: whether or not the return value is being passed directly
128  * to an HTTP operation
129  *
130  * Retrieves (in Cookie-header form) the list of cookies that would
131  * be sent with a request to @uri.
132  *
133  * If @for_http is %TRUE, the return value will include cookies marked
134  * "HttpOnly" (that is, cookies that the server wishes to keep hidden
135  * from client-side scripting operations such as the JavaScript
136  * document.cookies property). Since #SoupCookieJar sets the Cookie
137  * header itself when making the actual HTTP request, you should
138  * almost certainly be setting @for_http to %FALSE if you are calling
139  * this.
140  *
141  * Return value: the cookies, in string form, or %NULL if there are no
142  * cookies for @uri.
143  **/
144 char *
145 soup_cookie_jar_get_cookies (SoupCookieJar *jar, SoupURI *uri,
146                              gboolean for_http)
147 {
148         SoupCookieJarPrivate *priv;
149         GSList *cookies, *domain_cookies;
150         char *domain, *cur, *next, *result;
151
152         g_return_val_if_fail (SOUP_IS_COOKIE_JAR (jar), NULL);
153         priv = SOUP_COOKIE_JAR_GET_PRIVATE (jar);
154
155         /* The logic here is a little weird, but the plan is that if
156          * uri->host is "www.foo.com", we will end up looking up
157          * cookies for ".www.foo.com", "www.foo.com", ".foo.com", and
158          * ".com", in that order. (Logic stolen from Mozilla.)
159          */
160         cookies = NULL;
161         domain = cur = g_strdup_printf (".%s", uri->host);
162         next = domain + 1;
163         do {
164                 domain_cookies = g_hash_table_lookup (priv->domains, cur);
165                 while (domain_cookies) {
166                         SoupCookie *cookie = domain_cookies->data;
167
168                         if (soup_cookie_applies_to_uri (cookie, uri) &&
169                             (for_http || !cookie->http_only))
170                                 cookies = g_slist_append (cookies, cookie);
171                         domain_cookies = domain_cookies->next;
172                 }
173                 cur = next;
174                 if (cur)
175                         next = strchr (cur + 1, '.');
176         } while (cur);
177         g_free (domain);
178
179         if (cookies) {
180                 /* FIXME: sort? */
181                 result = soup_cookies_to_cookie_header (cookies);
182                 g_slist_free (cookies);
183                 return result;
184         } else
185                 return NULL;
186 }
187
188 static GSList *
189 get_cookies_for_domain (SoupCookieJar *jar, const char *domain)
190 {
191         SoupCookieJarPrivate *priv = SOUP_COOKIE_JAR_GET_PRIVATE (jar);
192         GSList *cookies, *orig_cookies, *c;
193         SoupCookie *cookie;
194
195         cookies = g_hash_table_lookup (priv->domains, domain);
196         c = orig_cookies = cookies;
197         while (c) {
198                 cookie = c->data;
199                 c = c->next;
200                 if (cookie->expires && soup_date_is_past (cookie->expires)) {
201                         cookies = g_slist_remove (cookies, cookie);
202                         soup_cookie_free (cookie);
203                 }
204         }
205
206         if (cookies != orig_cookies)
207                 g_hash_table_insert (priv->domains, g_strdup (domain), cookies);
208         return cookies;
209 }
210
211 static void
212 set_cookie (SoupCookieJar *jar, SoupCookie *cookie)
213 {
214         SoupCookieJarPrivate *priv = SOUP_COOKIE_JAR_GET_PRIVATE (jar);
215         GSList *old_cookies, *oc, *prev = NULL;
216         SoupCookie *old_cookie;
217
218         old_cookies = get_cookies_for_domain (jar, cookie->domain);
219         for (oc = old_cookies; oc; oc = oc->next) {
220                 old_cookie = oc->data;
221                 if (!strcmp (cookie->name, old_cookie->name)) {
222                         /* The new cookie is a replacement for an old
223                          * cookie. It might be pre-expired, but we
224                          * don't worry about that here;
225                          * get_cookies_for_domain() will delete it
226                          * later.
227                          */
228                         soup_cookie_free (old_cookie);
229                         oc->data = cookie;
230                         return;
231                 }
232                 prev = oc;
233         }
234
235         /* The new cookie is... a new cookie */
236         if (cookie->expires && soup_date_is_past (cookie->expires))
237                 soup_cookie_free (cookie);
238         else if (prev)
239                 prev = g_slist_append (prev, cookie);
240         else {
241                 old_cookies = g_slist_append (NULL, cookie);
242                 g_hash_table_insert (priv->domains, g_strdup (cookie->domain),
243                                      old_cookies);
244         }
245 }
246
247 /**
248  * soup_cookie_jar_set_cookie:
249  * @jar: a #SoupCookieJar
250  * @uri: the URI setting the cookie
251  * @cookie: the stringified cookie to set
252  *
253  * Adds @cookie to @jar, exactly as though it had appeared in a
254  * Set-Cookie header returned from a request to @uri.
255  **/
256 void
257 soup_cookie_jar_set_cookie (SoupCookieJar *jar, SoupURI *uri,
258                             const char *cookie)
259 {
260         SoupCookie *soup_cookie;
261
262         g_return_if_fail (SOUP_IS_COOKIE_JAR (jar));
263         g_return_if_fail (cookie != NULL);
264
265         soup_cookie = soup_cookie_parse (cookie, uri);
266         if (soup_cookie) {
267                 set_cookie (jar, soup_cookie);
268                 /* set_cookie will steal or free soup_cookie */
269         }
270 }
271
272 static void
273 process_set_cookie_header (SoupMessage *msg, gpointer user_data)
274 {
275         SoupCookieJar *jar = user_data;
276         GSList *new_cookies, *nc;
277
278         new_cookies = soup_cookies_from_response (msg);
279         for (nc = new_cookies; nc; nc = nc->next)
280                 set_cookie (jar, nc->data);
281         g_slist_free (new_cookies);
282 }
283
284 static void
285 request_queued (SoupSessionFeature *feature, SoupSession *session,
286                 SoupMessage *msg)
287 {
288         soup_message_add_header_handler (msg, "got-headers",
289                                          "Set-Cookie",
290                                          G_CALLBACK (process_set_cookie_header),
291                                          feature);
292 }
293
294 static void
295 request_started (SoupSessionFeature *feature, SoupSession *session,
296                  SoupMessage *msg, SoupSocket *socket)
297 {
298         SoupCookieJar *jar = SOUP_COOKIE_JAR (feature);
299         char *cookies;
300
301         cookies = soup_cookie_jar_get_cookies (jar, soup_message_get_uri (msg), TRUE);
302         soup_message_headers_replace (msg->request_headers,
303                                       "Cookie", cookies);
304         g_free (cookies);
305 }
306
307 static void
308 request_unqueued (SoupSessionFeature *feature, SoupSession *session,
309                   SoupMessage *msg)
310 {
311         g_signal_handlers_disconnect_by_func (msg, process_set_cookie_header, feature);
312 }
313