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