Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / camel / providers / imap / camel-imap-store-summary.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2002 Ximian Inc.
4  *
5  * Authors: Michael Zucchi <notzed@ximian.com>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of version 2 of the GNU Lesser General Public
9  * License as published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <ctype.h>
27 #include <errno.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31
32 #include <libedataserver/md5-utils.h>
33 #include <libedataserver/e-memory.h>
34
35 #include "camel-file-utils.h"
36 #include "camel-private.h"
37 #include "camel-store.h"
38 #include "camel-utf8.h"
39
40 #include "camel-imap-store-summary.h"
41
42 #define d(x)
43 #define io(x)                   /* io debug */
44
45 #define CAMEL_IMAP_STORE_SUMMARY_VERSION_0 (0)
46
47 #define CAMEL_IMAP_STORE_SUMMARY_VERSION (0)
48
49 #define _PRIVATE(o) (((CamelImapStoreSummary *)(o))->priv)
50
51 static void namespace_clear(CamelStoreSummary *s);
52
53 static int summary_header_load(CamelStoreSummary *, FILE *);
54 static int summary_header_save(CamelStoreSummary *, FILE *);
55
56 /*static CamelStoreInfo * store_info_new(CamelStoreSummary *, const char *);*/
57 static CamelStoreInfo * store_info_load(CamelStoreSummary *, FILE *);
58 static int               store_info_save(CamelStoreSummary *, FILE *, CamelStoreInfo *);
59 static void              store_info_free(CamelStoreSummary *, CamelStoreInfo *);
60
61 static const char *store_info_string(CamelStoreSummary *, const CamelStoreInfo *, int);
62 static void store_info_set_string(CamelStoreSummary *, CamelStoreInfo *, int, const char *);
63
64 static void camel_imap_store_summary_class_init (CamelImapStoreSummaryClass *klass);
65 static void camel_imap_store_summary_init       (CamelImapStoreSummary *obj);
66 static void camel_imap_store_summary_finalise   (CamelObject *obj);
67
68 static CamelStoreSummaryClass *camel_imap_store_summary_parent;
69
70 static void
71 camel_imap_store_summary_class_init (CamelImapStoreSummaryClass *klass)
72 {
73         CamelStoreSummaryClass *ssklass = (CamelStoreSummaryClass *)klass;
74
75         ssklass->summary_header_load = summary_header_load;
76         ssklass->summary_header_save = summary_header_save;
77
78         /*ssklass->store_info_new  = store_info_new;*/
79         ssklass->store_info_load = store_info_load;
80         ssklass->store_info_save = store_info_save;
81         ssklass->store_info_free = store_info_free;
82
83         ssklass->store_info_string = store_info_string;
84         ssklass->store_info_set_string = store_info_set_string;
85 }
86
87 static void
88 camel_imap_store_summary_init (CamelImapStoreSummary *s)
89 {
90         /*struct _CamelImapStoreSummaryPrivate *p;
91
92           p = _PRIVATE(s) = g_malloc0(sizeof(*p));*/
93
94         ((CamelStoreSummary *)s)->store_info_size = sizeof(CamelImapStoreInfo);
95         s->version = CAMEL_IMAP_STORE_SUMMARY_VERSION;
96 }
97
98 static void
99 camel_imap_store_summary_finalise (CamelObject *obj)
100 {
101         /*struct _CamelImapStoreSummaryPrivate *p;*/
102         /*CamelImapStoreSummary *s = (CamelImapStoreSummary *)obj;*/
103
104         /*p = _PRIVATE(obj);
105           g_free(p);*/
106 }
107
108 CamelType
109 camel_imap_store_summary_get_type (void)
110 {
111         static CamelType type = CAMEL_INVALID_TYPE;
112         
113         if (type == CAMEL_INVALID_TYPE) {
114                 camel_imap_store_summary_parent = (CamelStoreSummaryClass *)camel_store_summary_get_type();
115                 type = camel_type_register((CamelType)camel_imap_store_summary_parent, "CamelImapStoreSummary",
116                                            sizeof (CamelImapStoreSummary),
117                                            sizeof (CamelImapStoreSummaryClass),
118                                            (CamelObjectClassInitFunc) camel_imap_store_summary_class_init,
119                                            NULL,
120                                            (CamelObjectInitFunc) camel_imap_store_summary_init,
121                                            (CamelObjectFinalizeFunc) camel_imap_store_summary_finalise);
122         }
123         
124         return type;
125 }
126
127 /**
128  * camel_imap_store_summary_new:
129  *
130  * Create a new CamelImapStoreSummary object.
131  * 
132  * Return value: A new CamelImapStoreSummary widget.
133  **/
134 CamelImapStoreSummary *
135 camel_imap_store_summary_new (void)
136 {
137         CamelImapStoreSummary *new = CAMEL_IMAP_STORE_SUMMARY ( camel_object_new (camel_imap_store_summary_get_type ()));
138
139         return new;
140 }
141
142 /**
143  * camel_imap_store_summary_full_name:
144  * @s: 
145  * @path: 
146  * 
147  * Retrieve a summary item by full name.
148  *
149  * A referenced to the summary item is returned, which may be
150  * ref'd or free'd as appropriate.
151  * 
152  * Return value: The summary item, or NULL if the @full_name name
153  * is not available.
154  * It must be freed using camel_store_summary_info_free().
155  **/
156 CamelImapStoreInfo *
157 camel_imap_store_summary_full_name(CamelImapStoreSummary *s, const char *full_name)
158 {
159         int count, i;
160         CamelImapStoreInfo *info;
161
162         count = camel_store_summary_count((CamelStoreSummary *)s);
163         for (i=0;i<count;i++) {
164                 info = (CamelImapStoreInfo *)camel_store_summary_index((CamelStoreSummary *)s, i);
165                 if (info) {
166                         if (strcmp(info->full_name, full_name) == 0)
167                                 return info;
168                         camel_store_summary_info_free((CamelStoreSummary *)s, (CamelStoreInfo *)info);
169                 }
170         }
171
172         return NULL;
173 }
174
175 char *
176 camel_imap_store_summary_full_to_path(CamelImapStoreSummary *s, const char *full_name, char dir_sep)
177 {
178         char *path, *p;
179         int c;
180         const char *f;
181
182         if (dir_sep != '/') {
183                 p = path = alloca(strlen(full_name)*3+1);
184                 f = full_name;
185                 while ( (c = *f++ & 0xff) ) {
186                         if (c == dir_sep)
187                                 *p++ = '/';
188                         else if (c == '/' || c == '%')
189                                 p += sprintf(p, "%%%02X", c);
190                         else
191                                 *p++ = c;
192                 }
193                 *p = 0;
194         } else
195                 path = (char *)full_name;
196
197         return g_strdup(path);
198 }
199
200 static guint32 hexnib(guint32 c)
201 {
202         if (c >= '0' && c <= '9')
203                 return c-'0';
204         else if (c>='A' && c <= 'Z')
205                 return c-'A'+10;
206         else
207                 return 0;
208 }
209
210 char *
211 camel_imap_store_summary_path_to_full(CamelImapStoreSummary *s, const char *path, char dir_sep)
212 {
213         char *full, *f;
214         guint32 c, v = 0;
215         const char *p;
216         int state=0;
217         char *subpath, *last = NULL;
218         CamelStoreInfo *si;
219         CamelImapStoreNamespace *ns;
220
221         /* check to see if we have a subpath of path already defined */
222         subpath = alloca(strlen(path)+1);
223         strcpy(subpath, path);
224         do {
225                 si = camel_store_summary_path((CamelStoreSummary *)s, subpath);
226                 if (si == NULL) {
227                         last = strrchr(subpath, '/');
228                         if (last)
229                                 *last = 0;
230                 }
231         } while (si == NULL && last);
232
233         /* path is already present, use the raw version we have */
234         if (si && strlen(subpath) == strlen(path)) {
235                 f = g_strdup(camel_imap_store_info_full_name(s, si));
236                 camel_store_summary_info_free((CamelStoreSummary *)s, si);
237                 return f;
238         }
239
240         ns = camel_imap_store_summary_namespace_find_path(s, path);
241
242         f = full = alloca(strlen(path)*2+1);
243         if (si)
244                 p = path + strlen(subpath);
245         else if (ns)
246                 p = path + strlen(ns->path);
247         else
248                 p = path;
249
250         while ((c = camel_utf8_getc((const unsigned char **)&p))) {
251                 switch(state) {
252                 case 0:
253                         if (c == '%')
254                                 state = 1;
255                         else {
256                                 if (c == '/')
257                                         c = dir_sep;
258                                 camel_utf8_putc((unsigned char **) &f, c);
259                         }
260                         break;
261                 case 1:
262                         state = 2;
263                         v = hexnib(c)<<4;
264                         break;
265                 case 2:
266                         state = 0;
267                         v |= hexnib(c);
268                         camel_utf8_putc((unsigned char **) &f, v);
269                         break;
270                 }
271         }
272         camel_utf8_putc((unsigned char **) &f, c);
273
274         /* merge old path part if required */
275         f = g_strdup(full);
276         if (si) {
277                 full = g_strdup_printf("%s%s", camel_imap_store_info_full_name(s, si), f);
278                 g_free(f);
279                 camel_store_summary_info_free((CamelStoreSummary *)s, si);
280                 f = full;
281         } else if (ns) {
282                 full = g_strdup_printf("%s%s", ns->full_name, f);
283                 g_free(f);
284                 f = full;
285         }
286
287         return f;
288 }
289
290 CamelImapStoreInfo *
291 camel_imap_store_summary_add_from_full(CamelImapStoreSummary *s, const char *full, char dir_sep)
292 {
293         CamelImapStoreInfo *info;
294         char *pathu8, *prefix;
295         int len;
296         char *full_name;
297         CamelImapStoreNamespace *ns;
298
299         d(printf("adding full name '%s' '%c'\n", full, dir_sep));
300
301         len = strlen(full);
302         full_name = alloca(len+1);
303         strcpy(full_name, full);
304         if (full_name[len-1] == dir_sep)
305                 full_name[len-1] = 0;
306
307         info = camel_imap_store_summary_full_name(s, full_name);
308         if (info) {
309                 camel_store_summary_info_free((CamelStoreSummary *)s, (CamelStoreInfo *)info);
310                 d(printf("  already there\n"));
311                 return info;
312         }
313
314         ns = camel_imap_store_summary_namespace_find_full(s, full_name);
315         if (ns) {
316                 d(printf("(found namespace for '%s' ns '%s') ", full_name, ns->path));
317                 len = strlen(ns->full_name);
318                 if (len >= strlen(full_name)) {
319                         pathu8 = g_strdup(ns->path);
320                 } else {
321                         if (full_name[len] == ns->sep)
322                                 len++;
323                         
324                         prefix = camel_imap_store_summary_full_to_path(s, full_name+len, ns->sep);
325                         if (*ns->path) {
326                                 pathu8 = g_strdup_printf ("%s/%s", ns->path, prefix);
327                                 g_free (prefix);
328                         } else {
329                                 pathu8 = prefix;
330                         }
331                 }
332                 d(printf(" (pathu8 = '%s')", pathu8));
333         } else {
334                 d(printf("(Cannot find namespace for '%s')\n", full_name));
335                 pathu8 = camel_imap_store_summary_full_to_path(s, full_name, dir_sep);
336         }
337
338         info = (CamelImapStoreInfo *)camel_store_summary_add_from_path((CamelStoreSummary *)s, pathu8);
339         if (info) {
340                 d(printf("  '%s' -> '%s'\n", pathu8, full_name));
341                 camel_store_info_set_string((CamelStoreSummary *)s, (CamelStoreInfo *)info, CAMEL_IMAP_STORE_INFO_FULL_NAME, full_name);
342
343                 if (g_ascii_strcasecmp(full_name, "inbox"))
344                         info->info.flags |= CAMEL_FOLDER_SYSTEM|CAMEL_FOLDER_TYPE_INBOX;
345         } else
346                 d(printf("  failed\n"));
347
348         return info;
349 }
350
351 /* should this be const? */
352 /* TODO: deprecate/merge this function with path_to_full */
353 char *
354 camel_imap_store_summary_full_from_path(CamelImapStoreSummary *s, const char *path)
355 {
356         CamelImapStoreNamespace *ns;
357         char *name = NULL;
358
359         ns = camel_imap_store_summary_namespace_find_path(s, path);
360         if (ns)
361                 name = camel_imap_store_summary_path_to_full(s, path, ns->sep);
362
363         d(printf("looking up path %s -> %s\n", path, name?name:"not found"));
364
365         return name;
366 }
367
368 /* TODO: this api needs some more work */
369 CamelImapStoreNamespace *camel_imap_store_summary_namespace_new(CamelImapStoreSummary *s, const char *full_name, char dir_sep)
370 {
371         CamelImapStoreNamespace *ns;
372         char *p, *o, c;
373         int len;
374
375         ns = g_malloc0(sizeof(*ns));
376         ns->full_name = g_strdup(full_name);
377         len = strlen(ns->full_name)-1;
378         if (len >= 0 && ns->full_name[len] == dir_sep)
379                 ns->full_name[len] = 0;
380         ns->sep = dir_sep;
381
382         o = p = ns->path = camel_imap_store_summary_full_to_path(s, ns->full_name, dir_sep);
383         while ((c = *p++)) {
384                 if (c != '#') {
385                         if (c == '/')
386                                 c = '.';
387                         *o++ = c;
388                 }
389         }
390         *o = 0;
391
392         return ns;
393 }
394
395 void camel_imap_store_summary_namespace_set(CamelImapStoreSummary *s, CamelImapStoreNamespace *ns)
396 {
397         d(printf("Setting namesapce to '%s' '%c' -> '%s'\n", ns->full_name, ns->sep, ns->path));
398         namespace_clear((CamelStoreSummary *)s);
399         s->namespace = ns;
400         camel_store_summary_touch((CamelStoreSummary *)s);
401 }
402
403 CamelImapStoreNamespace *
404 camel_imap_store_summary_namespace_find_path(CamelImapStoreSummary *s, const char *path)
405 {
406         int len;
407         CamelImapStoreNamespace *ns;
408
409         /* NB: this currently only compares against 1 namespace, in future compare against others */
410         ns = s->namespace;
411         while (ns) {
412                 len = strlen(ns->path);
413                 if (len == 0
414                     || (strncmp(ns->path, path, len) == 0
415                         && (path[len] == '/' || path[len] == 0)))
416                         break;
417                 ns = NULL;
418         }
419
420         /* have a default? */
421         return ns;
422 }
423
424 CamelImapStoreNamespace *
425 camel_imap_store_summary_namespace_find_full(CamelImapStoreSummary *s, const char *full)
426 {
427         int len;
428         CamelImapStoreNamespace *ns;
429
430         /* NB: this currently only compares against 1 namespace, in future compare against others */
431         ns = s->namespace;
432         while (ns) {
433                 len = strlen(ns->full_name);
434                 d(printf("find_full: comparing namespace '%s' to name '%s'\n", ns->full_name, full));
435                 if (len == 0
436                     || (strncmp(ns->full_name, full, len) == 0
437                         && (full[len] == ns->sep || full[len] == 0)))
438                         break;
439                 ns = NULL;
440         }
441
442         /* have a default? */
443         return ns;
444 }
445
446 static void
447 namespace_free(CamelStoreSummary *s, CamelImapStoreNamespace *ns)
448 {
449         g_free(ns->path);
450         g_free(ns->full_name);
451         g_free(ns);
452 }
453
454 static void
455 namespace_clear(CamelStoreSummary *s)
456 {
457         CamelImapStoreSummary *is = (CamelImapStoreSummary *)s;
458
459         if (is->namespace)
460                 namespace_free(s, is->namespace);
461         is->namespace = NULL;
462 }
463
464 static CamelImapStoreNamespace *
465 namespace_load(CamelStoreSummary *s, FILE *in)
466 {
467         CamelImapStoreNamespace *ns;
468         guint32 sep = '/';
469
470         ns = g_malloc0(sizeof(*ns));
471         if (camel_file_util_decode_string(in, &ns->path) == -1
472             || camel_file_util_decode_string(in, &ns->full_name) == -1
473             || camel_file_util_decode_uint32(in, &sep) == -1) {
474                 namespace_free(s, ns);
475                 ns = NULL;
476         } else {
477                 ns->sep = sep;
478         }
479
480         return ns;
481 }
482
483 static int
484 namespace_save(CamelStoreSummary *s, FILE *in, CamelImapStoreNamespace *ns)
485 {
486         if (camel_file_util_encode_string(in, ns->path) == -1
487             || camel_file_util_encode_string(in, ns->full_name) == -1
488             || camel_file_util_encode_uint32(in, (guint32)ns->sep) == -1)
489                 return -1;
490
491         return 0;
492 }
493
494 static int
495 summary_header_load(CamelStoreSummary *s, FILE *in)
496 {
497         CamelImapStoreSummary *is = (CamelImapStoreSummary *)s;
498         gint32 version, capabilities, count;
499
500         namespace_clear(s);
501
502         if (camel_imap_store_summary_parent->summary_header_load((CamelStoreSummary *)s, in) == -1
503             || camel_file_util_decode_fixed_int32(in, &version) == -1)
504                 return -1;
505
506         is->version = version;
507
508         if (version < CAMEL_IMAP_STORE_SUMMARY_VERSION_0) {
509                 g_warning("Store summary header version too low");
510                 return -1;
511         }
512
513         /* note file format can be expanded to contain more namespaces, but only 1 at the moment */
514         if (camel_file_util_decode_fixed_int32(in, &capabilities) == -1
515             || camel_file_util_decode_fixed_int32(in, &count) == -1
516             || count > 1)
517                 return -1;
518
519         is->capabilities = capabilities;
520         if (count == 1) {
521                 if ((is->namespace = namespace_load(s, in)) == NULL)
522                         return -1;
523         }
524
525         return 0;
526 }
527
528 static int
529 summary_header_save(CamelStoreSummary *s, FILE *out)
530 {
531         CamelImapStoreSummary *is = (CamelImapStoreSummary *)s;
532         guint32 count;
533
534         count = is->namespace?1:0;
535
536         /* always write as latest version */
537         if (camel_imap_store_summary_parent->summary_header_save((CamelStoreSummary *)s, out) == -1
538             || camel_file_util_encode_fixed_int32(out, CAMEL_IMAP_STORE_SUMMARY_VERSION) == -1
539             || camel_file_util_encode_fixed_int32(out, is->capabilities) == -1
540             || camel_file_util_encode_fixed_int32(out, count) == -1)        
541                 return -1;
542
543         if (is->namespace && namespace_save(s, out, is->namespace) == -1)
544                 return -1;
545
546         return 0;
547 }
548
549 static CamelStoreInfo *
550 store_info_load(CamelStoreSummary *s, FILE *in)
551 {
552         CamelImapStoreInfo *mi;
553
554         mi = (CamelImapStoreInfo *)camel_imap_store_summary_parent->store_info_load(s, in);
555         if (mi) {
556                 if (camel_file_util_decode_string(in, &mi->full_name) == -1) {
557                         camel_store_summary_info_free(s, (CamelStoreInfo *)mi);
558                         mi = NULL;
559                 } else {
560                         /* NB: this is done again for compatability */
561                         if (g_ascii_strcasecmp(mi->full_name, "inbox") == 0)
562                                 mi->info.flags |= CAMEL_FOLDER_SYSTEM|CAMEL_FOLDER_TYPE_INBOX;
563                 }
564         }
565
566         return (CamelStoreInfo *)mi;
567 }
568
569 static int
570 store_info_save(CamelStoreSummary *s, FILE *out, CamelStoreInfo *mi)
571 {
572         CamelImapStoreInfo *isi = (CamelImapStoreInfo *)mi;
573
574         if (camel_imap_store_summary_parent->store_info_save(s, out, mi) == -1
575             || camel_file_util_encode_string(out, isi->full_name) == -1)
576                 return -1;
577
578         return 0;
579 }
580
581 static void
582 store_info_free(CamelStoreSummary *s, CamelStoreInfo *mi)
583 {
584         CamelImapStoreInfo *isi = (CamelImapStoreInfo *)mi;
585
586         g_free(isi->full_name);
587         camel_imap_store_summary_parent->store_info_free(s, mi);
588 }
589
590 static const char *
591 store_info_string(CamelStoreSummary *s, const CamelStoreInfo *mi, int type)
592 {
593         CamelImapStoreInfo *isi = (CamelImapStoreInfo *)mi;
594
595         /* FIXME: Locks? */
596
597         g_assert (mi != NULL);
598
599         switch (type) {
600         case CAMEL_IMAP_STORE_INFO_FULL_NAME:
601                 return isi->full_name;
602         default:
603                 return camel_imap_store_summary_parent->store_info_string(s, mi, type);
604         }
605 }
606
607 static void
608 store_info_set_string(CamelStoreSummary *s, CamelStoreInfo *mi, int type, const char *str)
609 {
610         CamelImapStoreInfo *isi = (CamelImapStoreInfo *)mi;
611
612         g_assert(mi != NULL);
613
614         switch(type) {
615         case CAMEL_IMAP_STORE_INFO_FULL_NAME:
616                 d(printf("Set full name %s -> %s\n", isi->full_name, str));
617                 CAMEL_STORE_SUMMARY_LOCK(s, summary_lock);
618                 g_free(isi->full_name);
619                 isi->full_name = g_strdup(str);
620                 CAMEL_STORE_SUMMARY_UNLOCK(s, summary_lock);
621                 break;
622         default:
623                 camel_imap_store_summary_parent->store_info_set_string(s, mi, type, str);
624                 break;
625         }
626 }