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