soup-auth-manager: add soup_auth_manager_use_auth()
[platform/upstream/libsoup.git] / libsoup / soup-cookie-jar-text.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * soup-cookie-jar-text.c: cookies.txt-based cookie storage
4  *
5  * Copyright (C) 2007, 2008 Red Hat, Inc.
6  */
7
8 #ifdef HAVE_CONFIG_H
9 #include <config.h>
10 #endif
11
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15
16 #include "soup-cookie-jar-text.h"
17 #include "soup.h"
18
19 /**
20  * SECTION:soup-cookie-jar-text
21  * @short_description: Text-file-based ("cookies.txt") Cookie Jar
22  *
23  * #SoupCookieJarText is a #SoupCookieJar that reads cookies from and
24  * writes them to a text file in the Mozilla "cookies.txt" format.
25  **/
26
27 enum {
28         PROP_0,
29
30         PROP_FILENAME,
31
32         LAST_PROP
33 };
34
35 typedef struct {
36         char *filename;
37
38 } SoupCookieJarTextPrivate;
39 #define SOUP_COOKIE_JAR_TEXT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_COOKIE_JAR_TEXT, SoupCookieJarTextPrivate))
40
41 G_DEFINE_TYPE (SoupCookieJarText, soup_cookie_jar_text, SOUP_TYPE_COOKIE_JAR)
42
43 static void load (SoupCookieJar *jar);
44
45 static void
46 soup_cookie_jar_text_init (SoupCookieJarText *text)
47 {
48 }
49
50 static void
51 soup_cookie_jar_text_finalize (GObject *object)
52 {
53         SoupCookieJarTextPrivate *priv =
54                 SOUP_COOKIE_JAR_TEXT_GET_PRIVATE (object);
55
56         g_free (priv->filename);
57
58         G_OBJECT_CLASS (soup_cookie_jar_text_parent_class)->finalize (object);
59 }
60
61 static void
62 soup_cookie_jar_text_set_property (GObject *object, guint prop_id,
63                                    const GValue *value, GParamSpec *pspec)
64 {
65         SoupCookieJarTextPrivate *priv =
66                 SOUP_COOKIE_JAR_TEXT_GET_PRIVATE (object);
67
68         switch (prop_id) {
69         case PROP_FILENAME:
70                 priv->filename = g_value_dup_string (value);
71                 load (SOUP_COOKIE_JAR (object));
72                 break;
73         default:
74                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
75                 break;
76         }
77 }
78
79 static void
80 soup_cookie_jar_text_get_property (GObject *object, guint prop_id,
81                                    GValue *value, GParamSpec *pspec)
82 {
83         SoupCookieJarTextPrivate *priv =
84                 SOUP_COOKIE_JAR_TEXT_GET_PRIVATE (object);
85
86         switch (prop_id) {
87         case PROP_FILENAME:
88                 g_value_set_string (value, priv->filename);
89                 break;
90         default:
91                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
92                 break;
93         }
94 }
95
96 /**
97  * soup_cookie_jar_text_new:
98  * @filename: the filename to read to/write from
99  * @read_only: %TRUE if @filename is read-only
100  *
101  * Creates a #SoupCookieJarText.
102  *
103  * @filename will be read in at startup to create an initial set of
104  * cookies. If @read_only is %FALSE, then the non-session cookies will
105  * be written to @filename when the 'changed' signal is emitted from
106  * the jar. (If @read_only is %TRUE, then the cookie jar will only be
107  * used for this session, and changes made to it will be lost when the
108  * jar is destroyed.)
109  *
110  * Return value: the new #SoupCookieJar
111  *
112  * Since: 2.26
113  **/
114 SoupCookieJar *
115 soup_cookie_jar_text_new (const char *filename, gboolean read_only)
116 {
117         g_return_val_if_fail (filename != NULL, NULL);
118
119         return g_object_new (SOUP_TYPE_COOKIE_JAR_TEXT,
120                              SOUP_COOKIE_JAR_TEXT_FILENAME, filename,
121                              SOUP_COOKIE_JAR_READ_ONLY, read_only,
122                              NULL);
123 }
124
125 static SoupCookie*
126 parse_cookie (char *line, time_t now)
127 {
128         char **result;
129         SoupCookie *cookie = NULL;
130         gboolean http_only;
131         gulong expire_time;
132         int max_age;
133         char *host, *path, *secure, *expires, *name, *value;
134
135         if (g_str_has_prefix (line, "#HttpOnly_")) {
136                 http_only = TRUE;
137                 line += strlen ("#HttpOnly_");
138         } else if (*line == '#' || g_ascii_isspace (*line))
139                 return cookie;
140         else
141                 http_only = FALSE;
142
143         result = g_strsplit (line, "\t", -1);
144         if (g_strv_length (result) != 7)
145                 goto out;
146
147         /* Check this first */
148         expires = result[4];
149         expire_time = strtoul (expires, NULL, 10);
150         if (now >= expire_time)
151                 goto out;
152         max_age = (expire_time - now <= G_MAXINT ? expire_time - now : G_MAXINT);
153
154         host = result[0];
155
156         /* result[1] is not used because it's redundat; it's a boolean
157          * value regarding whether the cookie should be used for
158          * sub-domains of the domain that is set for the cookie. It is
159          * TRUE if host starts with '.', and FALSE otherwise.
160          */
161
162         path = result[2];
163         secure = result[3];
164
165         name = result[5];
166         value = result[6];
167
168         cookie = soup_cookie_new (name, value, host, path, max_age);
169
170         if (strcmp (secure, "FALSE") != 0)
171                 soup_cookie_set_secure (cookie, TRUE);
172         if (http_only)
173                 soup_cookie_set_http_only (cookie, TRUE);
174
175  out:
176         g_strfreev (result);
177
178         return cookie;
179 }
180
181 static void
182 parse_line (SoupCookieJar *jar, char *line, time_t now)
183 {
184         SoupCookie *cookie;
185
186         cookie = parse_cookie (line, now);
187         if (cookie)
188                 soup_cookie_jar_add_cookie (jar, cookie);
189 }
190
191 static void
192 load (SoupCookieJar *jar)
193 {
194         SoupCookieJarTextPrivate *priv =
195                 SOUP_COOKIE_JAR_TEXT_GET_PRIVATE (jar);
196         char *contents = NULL, *line, *p;
197         gsize length = 0;
198         time_t now = time (NULL);
199
200         /* FIXME: error? */
201         if (!g_file_get_contents (priv->filename, &contents, &length, NULL))
202                 return;
203
204         line = contents;
205         for (p = contents; *p; p++) {
206                 /* \r\n comes out as an extra empty line and gets ignored */
207                 if (*p == '\r' || *p == '\n') {
208                         *p = '\0';
209                         parse_line (jar, line, now);
210                         line = p + 1;
211                 }
212         }
213         parse_line (jar, line, now);
214
215         g_free (contents);
216 }
217
218 static void
219 write_cookie (FILE *out, SoupCookie *cookie)
220 {
221         fseek (out, 0, SEEK_END);
222
223         fprintf (out, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
224                  cookie->http_only ? "#HttpOnly_" : "",
225                  cookie->domain,
226                  *cookie->domain == '.' ? "TRUE" : "FALSE",
227                  cookie->path,
228                  cookie->secure ? "TRUE" : "FALSE",
229                  (gulong)soup_date_to_time_t (cookie->expires),
230                  cookie->name,
231                  cookie->value);
232 }
233
234 static void
235 delete_cookie (const char *filename, SoupCookie *cookie)
236 {
237         char *contents = NULL, *line, *p;
238         gsize length = 0;
239         FILE *f;
240         SoupCookie *c;
241         time_t now = time (NULL);
242
243         if (!g_file_get_contents (filename, &contents, &length, NULL))
244                 return;
245
246         f = fopen (filename, "w");
247         if (!f) {
248                 g_free (contents);
249                 return;
250         }
251
252         line = contents;
253         for (p = contents; *p; p++) {
254                 /* \r\n comes out as an extra empty line and gets ignored */
255                 if (*p == '\r' || *p == '\n') {
256                         *p = '\0';
257                         c = parse_cookie (line, now);
258                         line = p + 1;
259                         if (!c)
260                                 continue;
261                         if (!soup_cookie_equal (cookie, c))
262                                 write_cookie (f, c);
263                         soup_cookie_free (c);
264                 }
265         }
266         c = parse_cookie (line, now);
267         if (c) {
268                 if (!soup_cookie_equal (cookie, c))
269                         write_cookie (f, c);
270                 soup_cookie_free (c);
271         }
272
273         g_free (contents);
274         fclose (f);
275 }
276
277 static void
278 soup_cookie_jar_text_changed (SoupCookieJar *jar,
279                               SoupCookie    *old_cookie,
280                               SoupCookie    *new_cookie)
281 {
282         FILE *out;
283         SoupCookieJarTextPrivate *priv =
284                 SOUP_COOKIE_JAR_TEXT_GET_PRIVATE (jar);
285
286         /* We can sort of ignore the semantics of the 'changed'
287          * signal here and simply delete the old cookie if present
288          * and write the new cookie if present. That will do the
289          * right thing for all 'added', 'deleted' and 'modified'
290          * meanings.
291          */
292         /* Also, delete_cookie takes the filename and write_cookie
293          * a FILE pointer. Seems more convenient that way considering
294          * the implementations of the functions
295          */
296         if (old_cookie)
297                 delete_cookie (priv->filename, old_cookie);
298
299         if (new_cookie) {
300                 gboolean write_header = FALSE;
301
302                 if (!g_file_test (priv->filename, G_FILE_TEST_EXISTS))
303                         write_header = TRUE;
304
305                 out = fopen (priv->filename, "a");
306                 if (!out) {
307                         /* FIXME: error? */
308                         return;
309                 }
310
311                 if (write_header) {
312                         fprintf (out, "# HTTP Cookie File\n");
313                         fprintf (out, "# http://www.netscape.com/newsref/std/cookie_spec.html\n");
314                         fprintf (out, "# This is a generated file!  Do not edit.\n");
315                         fprintf (out, "# To delete cookies, use the Cookie Manager.\n\n");
316                 }
317
318                 if (new_cookie->expires)
319                         write_cookie (out, new_cookie);
320
321                 if (fclose (out) != 0) {
322                         /* FIXME: error? */
323                         return;
324                 }
325         }
326 }
327
328 static gboolean
329 soup_cookie_jar_text_is_persistent (SoupCookieJar *jar)
330 {
331         return TRUE;
332 }
333
334 static void
335 soup_cookie_jar_text_class_init (SoupCookieJarTextClass *text_class)
336 {
337         SoupCookieJarClass *cookie_jar_class =
338                 SOUP_COOKIE_JAR_CLASS (text_class);
339         GObjectClass *object_class = G_OBJECT_CLASS (text_class);
340
341         g_type_class_add_private (text_class, sizeof (SoupCookieJarTextPrivate));
342
343         cookie_jar_class->is_persistent = soup_cookie_jar_text_is_persistent;
344         cookie_jar_class->changed       = soup_cookie_jar_text_changed;
345
346         object_class->finalize     = soup_cookie_jar_text_finalize;
347         object_class->set_property = soup_cookie_jar_text_set_property;
348         object_class->get_property = soup_cookie_jar_text_get_property;
349
350         /**
351          * SOUP_COOKIE_JAR_TEXT_FILENAME:
352          *
353          * Alias for the #SoupCookieJarText:filename property. (The
354          * cookie-storage filename.)
355          **/
356         g_object_class_install_property (
357                 object_class, PROP_FILENAME,
358                 g_param_spec_string (SOUP_COOKIE_JAR_TEXT_FILENAME,
359                                      "Filename",
360                                      "Cookie-storage filename",
361                                      NULL,
362                                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
363 }