Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / camel / providers / imapp / camel-imapp-store.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-imap-store.c : class for a imap store */
3
4 /* 
5  * Authors: Michael Zucchi <notzed@ximian.com>
6  *
7  * Copyright (C) 2000-2002 Ximian, Inc. (www.ximian.com)
8  *
9  * This program is free software; you can redistribute it and/or 
10  * modify it under the terms of version 2 of the GNU Lesser General Public 
11  * License as published by the Free Software Foundation.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
21  * USA
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <errno.h>
29 #include <errno.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34 #include <netinet/in.h>
35 #include <sys/socket.h>
36 #include <sys/types.h>
37
38 #include <glib/gi18n-lib.h>
39
40 #include "camel/camel-data-cache.h"
41 #include "camel/camel-exception.h"
42 #include "camel/camel-net-utils.h"
43 #include "camel/camel-operation.h"
44 #include "camel/camel-sasl.h"
45 #include "camel/camel-session.h"
46 #include "camel/camel-stream-buffer.h"
47 #include "camel/camel-tcp-stream-raw.h"
48 #include "camel/camel-tcp-stream.h"
49 #include "camel/camel-url.h"
50
51 #ifdef HAVE_SSL
52 #include "camel/camel-tcp-stream-ssl.h"
53 #endif
54
55 #include "camel-imapp-driver.h"
56 #include "camel-imapp-engine.h"
57 #include "camel-imapp-exception.h"
58 #include "camel-imapp-folder.h"
59 #include "camel-imapp-store-summary.h"
60 #include "camel-imapp-store.h"
61 #include "camel-imapp-utils.h"
62
63 /* Specified in RFC 2060 section 2.1 */
64 #define IMAP_PORT 143
65
66 static CamelStoreClass *parent_class = NULL;
67
68 static void finalize (CamelObject *object);
69
70 static void imap_construct(CamelService *service, CamelSession *session, CamelProvider *provider, CamelURL *url, CamelException *ex);
71 /* static char *imap_get_name(CamelService *service, gboolean brief);*/
72 static gboolean imap_connect (CamelService *service, CamelException *ex);
73 static gboolean imap_disconnect (CamelService *service, gboolean clean, CamelException *ex);
74 static GList *imap_query_auth_types (CamelService *service, CamelException *ex);
75
76 static CamelFolder *imap_get_trash  (CamelStore *store, CamelException *ex);
77
78 static CamelFolder *imap_get_folder(CamelStore * store, const char *folder_name, guint32 flags, CamelException * ex);
79 static CamelFolder *imap_get_inbox (CamelStore *store, CamelException *ex);
80 static void imap_rename_folder(CamelStore *store, const char *old_name, const char *new_name, CamelException *ex);
81 static CamelFolderInfo *imap_get_folder_info (CamelStore *store, const char *top, guint32 flags, CamelException *ex);
82 static void imap_delete_folder(CamelStore *store, const char *folder_name, CamelException *ex);
83 static void imap_rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex);
84 static CamelFolderInfo *imap_create_folder(CamelStore *store, const char *parent_name, const char *folder_name, CamelException *ex);
85
86 /* yet to see if this should go global or not */
87 void camel_imapp_store_folder_selected(CamelIMAPPStore *store, CamelIMAPPFolder *folder, CamelIMAPPSelectResponse *select);
88
89 static void
90 camel_imapp_store_class_init (CamelIMAPPStoreClass *camel_imapp_store_class)
91 {
92         CamelServiceClass *camel_service_class = CAMEL_SERVICE_CLASS(camel_imapp_store_class);
93         CamelStoreClass *camel_store_class = CAMEL_STORE_CLASS(camel_imapp_store_class);
94
95         parent_class = CAMEL_STORE_CLASS(camel_type_get_global_classfuncs(camel_store_get_type()));
96         
97         /* virtual method overload */
98         camel_service_class->construct = imap_construct;
99         /*camel_service_class->get_name = imap_get_name;*/
100         camel_service_class->query_auth_types = imap_query_auth_types;
101         camel_service_class->connect = imap_connect;
102         camel_service_class->disconnect = imap_disconnect;
103
104         camel_store_class->get_trash = imap_get_trash;
105         camel_store_class->get_folder = imap_get_folder;
106         camel_store_class->get_inbox = imap_get_inbox;
107
108         camel_store_class->create_folder = imap_create_folder;
109         camel_store_class->rename_folder = imap_rename_folder;
110         camel_store_class->delete_folder = imap_delete_folder;
111         camel_store_class->get_folder_info = imap_get_folder_info;
112 }
113
114 static void
115 camel_imapp_store_init (gpointer object, gpointer klass)
116 {
117         /*CamelIMAPPStore *istore = object;*/
118 }
119
120 CamelType
121 camel_imapp_store_get_type (void)
122 {
123         static CamelType camel_imapp_store_type = CAMEL_INVALID_TYPE;
124
125         if (!camel_imapp_store_type) {
126                 camel_imapp_store_type = camel_type_register(CAMEL_STORE_TYPE,
127                                                             "CamelIMAPPStore",
128                                                             sizeof (CamelIMAPPStore),
129                                                             sizeof (CamelIMAPPStoreClass),
130                                                             (CamelObjectClassInitFunc) camel_imapp_store_class_init,
131                                                             NULL,
132                                                             (CamelObjectInitFunc) camel_imapp_store_init,
133                                                             finalize);
134         }
135
136         return camel_imapp_store_type;
137 }
138
139 static void
140 finalize (CamelObject *object)
141 {
142         CamelIMAPPStore *imap_store = CAMEL_IMAPP_STORE (object);
143
144         /* force disconnect so we dont have it run later, after we've cleaned up some stuff */
145         /* SIGH */
146
147         camel_service_disconnect((CamelService *)imap_store, TRUE, NULL);
148
149         if (imap_store->driver)
150                 camel_object_unref(imap_store->driver);
151         if (imap_store->cache)
152                 camel_object_unref(imap_store->cache);
153 }
154
155 static void imap_construct(CamelService *service, CamelSession *session, CamelProvider *provider, CamelURL *url, CamelException *ex)
156 {
157         char *root, *summary;
158         CamelIMAPPStore *store = (CamelIMAPPStore *)service;
159
160         CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex);
161         if (camel_exception_is_set(ex))
162                 return;
163         
164         CAMEL_TRY {
165                 store->summary = camel_imapp_store_summary_new();
166                 root = camel_session_get_storage_path(service->session, service, ex);
167                 if (root) {
168                         summary = g_build_filename(root, ".ev-store-summary", NULL);
169                         camel_store_summary_set_filename((CamelStoreSummary *)store->summary, summary);
170                         /* FIXME: need to remove params, passwords, etc */
171                         camel_store_summary_set_uri_base((CamelStoreSummary *)store->summary, service->url);
172                         camel_store_summary_load((CamelStoreSummary *)store->summary);
173                 }
174         } CAMEL_CATCH(e) {
175                 camel_exception_xfer(ex, e);
176         } CAMEL_DONE;
177 }
178
179 enum {
180         USE_SSL_NEVER,
181         USE_SSL_ALWAYS,
182         USE_SSL_WHEN_POSSIBLE
183 };
184
185 #define SSL_PORT_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3)
186 #define STARTTLS_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_TLS)
187
188 static void
189 connect_to_server (CamelService *service, int ssl_mode, int try_starttls)
190 /* throws IO exception */
191 {
192         CamelIMAPPStore *store = CAMEL_IMAPP_STORE (service);
193         CamelStream * volatile tcp_stream = NULL;
194         CamelIMAPPStream * volatile imap_stream = NULL;
195         int ret;
196         CamelException *ex;
197
198         ex = camel_exception_new();
199         CAMEL_TRY {
200                 char *serv;
201                 const char *port = NULL;
202                 struct addrinfo *ai, hints = { 0 };
203
204                 /* parent class connect initialization */
205                 CAMEL_SERVICE_CLASS (parent_class)->connect (service, ex);
206                 if (ex->id)
207                         camel_exception_throw_ex(ex);
208
209                 if (service->url->port) {
210                         serv = g_alloca(16);
211                         sprintf(serv, "%d", service->url->port);
212                 } else {
213                         serv = "imap";
214                         port = "143";
215                 }
216
217 #ifdef HAVE_SSL 
218                 if (camel_url_get_param (service->url, "use_ssl")) {
219                         if (try_starttls)
220                                 tcp_stream = camel_tcp_stream_ssl_new_raw (service->session, service->url->host, STARTTLS_FLAGS);
221                         else {
222                                 if (service->url->port == 0) {
223                                         serv = "imaps";
224                                         port = "993";
225                                 }
226                                 tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, SSL_PORT_FLAGS);
227                         }
228                 } else {
229                         tcp_stream = camel_tcp_stream_raw_new ();
230                 }
231 #else   
232                 tcp_stream = camel_tcp_stream_raw_new ();
233 #endif /* HAVE_SSL */
234
235                 hints.ai_socktype = SOCK_STREAM;
236                 ai = camel_getaddrinfo(service->url->host, serv, &hints, ex);
237                 if (ex->id && ex->id != CAMEL_EXCEPTION_USER_CANCEL && port != NULL) {
238                         camel_exception_clear(ex);
239                         ai = camel_getaddrinfo(service->url->host, port, &hints, ex);
240                 }
241
242                 if (ex->id)
243                         camel_exception_throw_ex(ex);
244         
245                 ret = camel_tcp_stream_connect(CAMEL_TCP_STREAM(tcp_stream), ai);
246                 camel_freeaddrinfo(ai);
247                 if (ret == -1) {
248                         if (errno == EINTR)
249                                 camel_exception_throw(CAMEL_EXCEPTION_USER_CANCEL, _("Connection canceled"));
250                         else
251                                 camel_exception_throw(CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
252                                                       _("Could not connect to %s (port %s): %s"),
253                                                       service->url->host, serv, strerror(errno));
254                 }
255
256                 imap_stream = (CamelIMAPPStream *)camel_imapp_stream_new(tcp_stream);
257                 store->driver = camel_imapp_driver_new(imap_stream);
258
259                 camel_object_unref(imap_stream);
260                 camel_object_unref(tcp_stream);
261         } CAMEL_CATCH(e) {
262                 if (tcp_stream)
263                         camel_object_unref(tcp_stream);
264                 if (imap_stream)
265                         camel_object_unref((CamelObject *)imap_stream);
266                 camel_exception_throw_ex(e);
267         } CAMEL_DONE;
268
269         camel_exception_free(ex);
270 }
271
272 #if 0
273
274 /* leave this stuff out for now */
275
276
277 static struct {
278         char *value;
279         int mode;
280 } ssl_options[] = {
281         { "",              USE_SSL_ALWAYS        },
282         { "always",        USE_SSL_ALWAYS        },
283         { "when-possible", USE_SSL_WHEN_POSSIBLE },
284         { "never",         USE_SSL_NEVER         },
285         { NULL,            USE_SSL_NEVER         },
286 };
287
288 static gboolean
289 connect_to_server_wrapper (CamelService *service, CamelException *ex)
290 {
291 #ifdef HAVE_SSL
292         const char *use_ssl;
293         int i, ssl_mode;
294         
295         use_ssl = camel_url_get_param (service->url, "use_ssl");
296         if (use_ssl) {
297                 for (i = 0; ssl_options[i].value; i++)
298                         if (!strcmp (ssl_options[i].value, use_ssl))
299                                 break;
300                 ssl_mode = ssl_options[i].mode;
301         } else
302                 ssl_mode = USE_SSL_NEVER;
303         
304         if (ssl_mode == USE_SSL_ALWAYS) {
305                 /* First try the ssl port */
306                 if (!connect_to_server (service, ssl_mode, FALSE, ex)) {
307                         if (camel_exception_get_id (ex) == CAMEL_EXCEPTION_SERVICE_UNAVAILABLE) {
308                                 /* The ssl port seems to be unavailable, lets try STARTTLS */
309                                 camel_exception_clear (ex);
310                                 return connect_to_server (service, ssl_mode, TRUE, ex);
311                         } else {
312                                 return FALSE;
313                         }
314                 }
315                 
316                 return TRUE;
317         } else if (ssl_mode == USE_SSL_WHEN_POSSIBLE) {
318                 /* If the server supports STARTTLS, use it */
319                 return connect_to_server (service, ssl_mode, TRUE, ex);
320         } else {
321                 /* User doesn't care about SSL */
322                 return connect_to_server (service, ssl_mode, FALSE, ex);
323         }
324 #else
325         return connect_to_server (service, USE_SSL_NEVER, FALSE, ex);
326 #endif
327 }
328 #endif
329
330 extern CamelServiceAuthType camel_imapp_password_authtype;
331 extern CamelServiceAuthType camel_imapp_apop_authtype;
332
333 static GList *
334 imap_query_auth_types (CamelService *service, CamelException *ex)
335 {
336         /*CamelIMAPPStore *store = CAMEL_IMAPP_STORE (service);*/
337         GList *types = NULL;
338
339         types = CAMEL_SERVICE_CLASS (parent_class)->query_auth_types (service, ex);
340         if (types == NULL)
341                 return NULL;
342
343 #if 0
344         if (connect_to_server_wrapper (service, NULL)) {
345                 types = g_list_concat(types, g_list_copy(store->engine->auth));
346                 imap_disconnect (service, TRUE, NULL);
347         } else {
348                 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
349                                       _("Could not connect to POP server on %s"),
350                                       service->url->host);
351         }
352 #endif
353         return types;
354 }
355
356 static void
357 store_get_pass(CamelIMAPPStore *store)
358 {
359         if (((CamelService *)store)->url->passwd == NULL) {
360                 char *prompt;
361                 CamelException ex;
362
363                 camel_exception_init(&ex);
364                 
365                 prompt = g_strdup_printf (_("%sPlease enter the IMAP password for %s@%s"),
366                                           store->login_error?store->login_error:"",
367                                           ((CamelService *)store)->url->user,
368                                           ((CamelService *)store)->url->host);
369                 ((CamelService *)store)->url->passwd = camel_session_get_password(camel_service_get_session((CamelService *)store),
370                                                                                   (CamelService *)store, NULL,
371                                                                                   prompt, "password", CAMEL_SESSION_PASSWORD_SECRET, &ex);
372                 g_free (prompt);
373                 if (camel_exception_is_set(&ex))
374                         camel_exception_throw_ex(&ex);
375         }
376 }
377
378 static struct _CamelSasl *
379 store_get_sasl(struct _CamelIMAPPDriver *driver, CamelIMAPPStore *store)
380 {
381         store_get_pass(store);
382
383         if (((CamelService *)store)->url->authmech)
384                 return camel_sasl_new("imap", ((CamelService *)store)->url->authmech, (CamelService *)store);
385
386         return NULL;
387 }
388
389 static void
390 store_get_login(struct _CamelIMAPPDriver *driver, char **login, char **pass, CamelIMAPPStore *store)
391 {
392         store_get_pass(store);
393         
394         *login = g_strdup(((CamelService *)store)->url->user);
395         *pass = g_strdup(((CamelService *)store)->url->passwd);
396 }
397
398 static gboolean
399 imap_connect (CamelService *service, CamelException *ex)
400 {
401         CamelIMAPPStore *store = (CamelIMAPPStore *)service;
402         volatile int ret = FALSE;
403
404         CAMEL_TRY {
405                 volatile int retry = TRUE;
406
407                 if (store->cache == NULL) {
408                         char *root;
409                         
410                         root = camel_session_get_storage_path(service->session, service, ex);
411                         if (root) {
412                                 store->cache = camel_data_cache_new(root, 0, ex);
413                                 g_free(root);
414                                 if (store->cache) {
415                                         /* Default cache expiry - 1 week or not visited in a day */
416                                         camel_data_cache_set_expire_age(store->cache, 60*60*24*7);
417                                         camel_data_cache_set_expire_access(store->cache, 60*60*24);
418                                 }
419                         }
420                         if (camel_exception_is_set(ex))
421                                 camel_exception_throw_ex(ex);
422                 }
423
424                 connect_to_server(service, USE_SSL_NEVER, FALSE);
425
426                 camel_imapp_driver_set_sasl_factory(store->driver, (CamelIMAPPSASLFunc)store_get_sasl, store);
427                 camel_imapp_driver_set_login_query(store->driver, (CamelIMAPPLoginFunc)store_get_login, store);
428                 store->login_error = NULL;
429
430                 do {
431                         CAMEL_TRY {
432                                 if (store->driver->engine->state != IMAP_ENGINE_AUTH)
433                                         camel_imapp_driver_login(store->driver);
434                                 ret = TRUE;
435                                 retry = FALSE;
436                         } CAMEL_CATCH(e) {
437                                 g_free(store->login_error);
438                                 store->login_error = NULL;
439                                 switch (e->id) {
440                                 case CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE:
441                                         store->login_error = g_strdup_printf("%s\n\n", e->desc);
442                                         camel_session_forget_password(service->session, service, NULL, "password", ex);
443                                         camel_url_set_passwd(service->url, NULL);
444                                         break;
445                                 default:
446                                         camel_exception_throw_ex(e);
447                                         break;
448                                 }
449                         } CAMEL_DONE;
450                 } while (retry);
451         } CAMEL_CATCH(e) {
452                 camel_exception_xfer(ex, e);
453                 camel_service_disconnect(service, TRUE, NULL);
454                 ret = FALSE;
455         } CAMEL_DONE;
456
457         g_free(store->login_error);
458         store->login_error = NULL;
459
460         return ret;
461 }
462
463 static gboolean
464 imap_disconnect (CamelService *service, gboolean clean, CamelException *ex)
465 {
466         CamelIMAPPStore *store = CAMEL_IMAPP_STORE (service);
467
468         /* FIXME: logout */
469         
470         if (!CAMEL_SERVICE_CLASS (parent_class)->disconnect (service, clean, ex))
471                 return FALSE;
472
473         /* logout/disconnect */
474         if (store->driver) {
475                 camel_object_unref(store->driver);
476                 store->driver = NULL;
477         }
478         
479         return TRUE;
480 }
481
482 static CamelFolder *
483 imap_get_trash (CamelStore *store, CamelException *ex)
484 {
485         /* no-op */
486         return NULL;
487 }
488
489 static CamelFolder *
490 imap_get_folder (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex)
491 {
492         CamelIMAPPStore *istore = (CamelIMAPPStore *)store;
493         CamelIMAPPFolder * volatile folder = NULL;
494
495         /* ??? */
496
497         /* 1. create the folder */
498         /* 2. run select? */
499         /* 3. update the folder */
500
501         CAMEL_TRY {
502                 folder = (CamelIMAPPFolder *)camel_imapp_folder_new(store, folder_name);
503                 camel_imapp_driver_select(istore->driver, folder);
504         } CAMEL_CATCH (e) {
505                 if (folder) {
506                         camel_object_unref(folder);
507                         folder = NULL;
508                 }
509                 camel_exception_xfer(ex, e);
510         } CAMEL_DONE;
511
512         return (CamelFolder *)folder;
513 }
514
515 static CamelFolder *
516 imap_get_inbox(CamelStore *store, CamelException *ex)
517 {
518         camel_exception_setv(ex, 1, "get_inbox::unimplemented");
519
520         return NULL;
521 }
522
523 /* 8 bit, string compare */
524 static int folders_build_cmp(const void *app, const void *bpp)
525 {
526         struct _list_info *a = *((struct _list_info **)app);
527         struct _list_info *b = *((struct _list_info **)bpp);
528         unsigned char *ap = (unsigned char *)(a->name);
529         unsigned char *bp = (unsigned char *)(b->name);
530
531         printf("qsort, cmp '%s' <> '%s'\n", ap, bp);
532
533         while (*ap && *ap == *bp) {
534                 ap++;
535                 bp++;
536         }
537
538         if (*ap < *bp)
539                 return -1;
540         else if (*ap > *bp)
541                 return 1;
542         return 0;
543 }
544
545 /* FIXME: this should go via storesummary? */
546 static CamelFolderInfo *
547 folders_build_info(CamelURL *base, struct _list_info *li)
548 {
549         char *path, *full_name, *name;
550         CamelFolderInfo *fi;
551
552         full_name = imapp_list_get_path(li);
553         name = strrchr(full_name, '/');
554         if (name)
555                 name++;
556         else
557                 name = full_name;
558
559         path = alloca(strlen(full_name)+2);
560         sprintf(path, "/%s", full_name);
561         camel_url_set_path(base, path);
562
563         fi = g_malloc0(sizeof(*fi));
564         fi->uri = camel_url_to_string(base, CAMEL_URL_HIDE_ALL);
565         fi->name = g_strdup(name);
566         fi->full_name = full_name;
567         fi->unread = -1;
568         fi->total = -1;
569         fi->flags = li->flags;
570
571         if (!g_ascii_strcasecmp(fi->full_name, "inbox"))
572                 fi->flags |= CAMEL_FOLDER_SYSTEM;
573
574         /* TODO: could look up count here ... */
575         /* ?? */
576         /*folder = camel_object_bag_get(store->folders, "INBOX");*/
577
578         return fi;
579 }
580
581 /*
582   a
583   a/b
584   a/b/c
585   a/d
586   b
587   c/d
588
589 */
590
591 /* note, pname is the raw name, not the folderinfo name */
592 /* note also this free's as we go, since we never go 'backwards' */
593 static CamelFolderInfo *
594 folders_build_rec(CamelURL *base, GPtrArray *folders, int *ip, CamelFolderInfo *pfi, char *pname)
595 {
596         int plen = 0;
597         CamelFolderInfo *last = NULL, *first = NULL;
598
599         if (pfi)
600                 plen = strlen(pname);
601
602         for(;(*ip)<(int)folders->len;) {
603                 CamelFolderInfo *fi;
604                 struct _list_info *li;
605
606                 li = folders->pdata[*ip];
607                 printf("checking '%s' is child of '%s'\n", li->name, pname);
608
609                 /* is this a child of the parent? */
610                 if (pfi != NULL
611                     && (strncmp(pname, li->name, strlen(pname)) != 0
612                         || li->name[plen] != li->separator)) {
613                         printf("  nope\n");
614                         break;
615                 }
616                 printf("  yep\n");
617
618                 /* is this not an immediate child of the parent? */
619 #if 0
620                 char *p;
621                 if (pfi != NULL
622                     && li->separator != 0
623                     && (p = strchr(li->name + plen + 1, li->separator)) != NULL) {
624                         if (last == NULL) {
625                                 struct _list_info tli;
626
627                                 tli.flags = CAMEL_FOLDER_NOSELECT|CAMEL_FOLDER_CHILDREN;
628                                 tli.separator = li->separator;
629                                 tli.name = g_strndup(li->name, p-li->name+1);
630                                 fi = folders_build_info(base, &tli);
631                                 fi->parent = pfi;
632                                 if (pfi && pfi->child == NULL)
633                                         pfi->child = fi;
634                                 i = folders_build_rec(folders, i, fi, tli.name);
635                                 break;
636                         }
637                 }
638 #endif
639
640                 fi = folders_build_info(base, li);
641                 fi->parent = pfi;
642                 if (last != NULL)
643                         last->next = fi;
644                 last = fi;
645                 if (first == NULL)
646                         first = fi;
647
648                 (*ip)++;
649                 fi->child = folders_build_rec(base, folders, ip, fi, li->name);
650                 imap_free_list(li);
651         }
652
653         return first;
654 }
655
656 static void
657 folder_info_dump(CamelFolderInfo *fi, int depth)
658 {
659         char *s;
660
661         s = alloca(depth+1);
662         memset(s, ' ', depth);
663         s[depth] = 0;
664         while (fi) {
665                 printf("%s%s (%s)\n", s, fi->name, fi->uri);
666                 if (fi->child)
667                         folder_info_dump(fi->child, depth+2);
668                 fi = fi->next;
669         }
670         
671 }
672
673 static CamelFolderInfo *
674 imap_get_folder_info(CamelStore *store, const char *top, guint32 flags, CamelException *ex)
675 {
676         CamelIMAPPStore *istore = (CamelIMAPPStore *)store;
677         CamelFolderInfo * fi= NULL;
678         char *name;
679
680         /* FIXME: temporary, since this is not a disco store */
681         if (istore->driver == NULL
682             && !camel_service_connect((CamelService *)store, ex))
683                 return NULL;
684
685         name = (char *)top;
686         if (name == NULL || name[0] == 0) {
687                 /* namespace? */
688                 name = "";
689         }
690
691         name = "";
692
693         CAMEL_TRY {
694                 CamelURL *base;
695                 int i;
696                 GPtrArray *folders;
697
698                 /* FIXME: subscriptions? lsub? */
699                 folders = camel_imapp_driver_list(istore->driver, name, flags);
700
701                 /* this greatly simplifies the tree algorithm ... but it might
702                    be faster just to use a hashtable to find parents? */
703                 qsort(folders->pdata, folders->len, sizeof(folders->pdata[0]), folders_build_cmp);
704
705                 i = 0;
706                 base = camel_url_copy(((CamelService *)store)->url);
707                 fi = folders_build_rec(base, folders, &i, NULL, NULL);
708                 camel_url_free(base);
709                 g_ptr_array_free(folders, TRUE);
710         } CAMEL_CATCH(e) {
711                 camel_exception_xfer(ex, e);
712         } CAMEL_DONE;
713
714         printf("built folder info:\n");
715         folder_info_dump(fi, 2);
716
717         return fi;
718
719 #if 0
720         if (top == NULL || !g_ascii_strcasecmp(top, "inbox")) {
721                 CamelURL *uri = camel_url_copy(((CamelService *)store)->url);
722
723                 camel_url_set_path(uri, "/INBOX");
724                 fi = g_malloc0(sizeof(*fi));
725                 fi->url = camel_url_to_string(uri, CAMEL_URL_HIDE_ALL);
726                 camel_url_free(uri);
727                 fi->name = g_strdup("INBOX");
728                 fi->full_name = g_strdup("INBOX");
729                 fi->path = g_strdup("/INBOX");
730                 fi->unread_message_count = -1;
731                 fi->flags = 0;
732
733                 folder = camel_object_bag_get(store->folders, "INBOX");
734                 if (folder) {
735                         /*if (!cflags & FAST)*/
736                         camel_imapp_driver_update(istore->driver, (CamelIMAPPFolder *)folder);
737                         fi->unread_message_count = camel_folder_get_unread_message_count(folder);
738                         camel_object_unref(folder);
739                 }
740         } else {
741                 camel_exception_setv(ex, 1, "not implemented");
742         }
743 #endif
744         return fi;
745
746 #if 0
747         istore->pending_list = g_ptr_array_new();
748
749         CAMEL_TRY {
750                 ic = camel_imapp_engine_command_new(istore->driver->engine, "LIST", NULL, "LIST \"\" %f", top);
751                 camel_imapp_engine_command_queue(istore->driver->engine, ic);
752                 while (camel_imapp_engine_iterate(istore->driver->engine, ic) > 0)
753                         ;
754
755                 if (ic->status->result != IMAP_OK)
756                         camel_exception_throw(1, "list failed: %s", ic->status->text);
757         } CAMEL_CATCH (e) {
758                 camel_exception_xfer(ex, e);
759         } CAMEL_DONE;
760         
761         camel_imapp_engine_command_free(istore->driver->engine, ic);
762
763         printf("got folder list:\n");
764         for (i=0;i<(int)istore->pending_list->len;i++) {
765                 struct _list_info *linfo = istore->pending_list->pdata[i];
766
767                 printf("%s (%c)\n", linfo->name, linfo->separator);
768                 imap_free_list(linfo);
769         }
770         istore->pending_list = NULL;
771
772         return NULL;
773 #endif
774 }
775
776 static void
777 imap_delete_folder(CamelStore *store, const char *folder_name, CamelException *ex)
778 {
779         camel_exception_setv(ex, 1, "delete_folder::unimplemented");
780 }
781
782 static void
783 imap_rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex)
784 {
785         camel_exception_setv(ex, 1, "rename_folder::unimplemented");
786 }
787
788 static CamelFolderInfo *
789 imap_create_folder(CamelStore *store, const char *parent_name, const char *folder_name, CamelException *ex)
790 {
791         camel_exception_setv(ex, 1, "create_folder::unimplemented");
792         return NULL;
793 }
794
795 /* ********************************************************************** */
796 #if 0
797 static int store_resp_fetch(CamelIMAPPEngine *ie, guint32 id, void *data)
798 {
799         struct _fetch_info *finfo;
800         CamelIMAPPStore *istore = data;
801         CamelMessageInfo *info;
802         struct _pending_fetch *pending;
803
804         finfo = imap_parse_fetch(ie->stream);
805         if (istore->selected) {
806                 if ((finfo->got & FETCH_UID) == 0) {
807                         printf("didn't get uid in fetch response?\n");
808                 } else {
809                         info = camel_folder_summary_index(((CamelFolder *)istore->selected)->summary, id-1);
810                         /* exists, check/update */
811                         if (info) {
812                                 if (strcmp(finfo->uid, camel_message_info_uid(info)) != 0) {
813                                         printf("summary at index %d has uid %s expected %s\n", id, camel_message_info_uid(info), finfo->uid);
814                                         /* uid mismatch???  try do it based on uid instead? try to reorder?  i dont know? */
815                                         camel_message_info_free(info);
816                                         info = camel_folder_summary_uid(((CamelFolder *)istore->selected)->summary, finfo->uid);
817                                 }
818                         }
819
820                         if (info) {
821                                 if (finfo->got & (FETCH_FLAGS)) {
822                                         printf("updating flags for uid '%s'\n", finfo->uid);
823                                         info->flags = finfo->flags;
824                                         camel_folder_change_info_change_uid(istore->selected->changes, finfo->uid);
825                                 }
826                                 if (finfo->got & FETCH_MINFO) {
827                                         printf("got envelope unexpectedly?\n");
828                                 }
829                                 /* other things go here, like body fetches */
830                         } else {
831                                 pending = g_hash_table_lookup(istore->pending_fetch_table, finfo->uid);
832
833                                 /* we need to create a new info, we only care about flags and minfo */
834
835                                 if (pending)
836                                         info = pending->info;
837                                 else {
838                                         info = camel_folder_summary_info_new(((CamelFolder *)istore->selected)->summary);
839                                         camel_message_info_set_uid(info, g_strdup(finfo->uid));
840                                 }
841
842                                 if (finfo->got & FETCH_FLAGS)
843                                         info->flags = finfo->flags;
844
845                                 if (finfo->got & FETCH_MINFO) {
846                                         /* if we only use ENVELOPE? */
847                                         camel_message_info_set_subject(info, g_strdup(camel_message_info_subject(finfo->minfo)));
848                                         camel_message_info_set_from(info, g_strdup(camel_message_info_from(finfo->minfo)));
849                                         camel_message_info_set_to(info, g_strdup(camel_message_info_to(finfo->minfo)));
850                                         camel_message_info_set_cc(info, g_strdup(camel_message_info_cc(finfo->minfo)));
851                                         info->date_sent = finfo->minfo->date_sent;
852                                         camel_folder_summary_add(((CamelFolder *)istore->selected)->summary, info);
853                                         camel_folder_change_info_add_uid(istore->selected->changes, finfo->uid);
854                                         if (pending) {
855                                                 e_dlist_remove((EDListNode *)pending);
856                                                 g_hash_table_remove(istore->pending_fetch_table, finfo->uid);
857                                                 /*e_memchunk_free(istore->pending_fetch_chunks, pending);*/
858                                         }
859                                 } else if (finfo->got & FETCH_HEADER) {
860                                         /* if we only use HEADER? */
861                                         CamelMimeParser *mp;
862
863                                         if (pending == NULL)
864                                                 camel_message_info_free(info);
865                                         mp = camel_mime_parser_new();
866                                         camel_mime_parser_init_with_stream(mp, finfo->header);
867                                         info = camel_folder_summary_info_new_from_parser(((CamelFolder *)istore->selected)->summary, mp);
868                                         camel_object_unref(mp);
869                                         camel_message_info_set_uid(info, g_strdup(finfo->uid));
870
871                                         camel_folder_summary_add(((CamelFolder *)istore->selected)->summary, info);
872                                         camel_folder_change_info_add_uid(istore->selected->changes, finfo->uid);
873                                         if (pending) {
874                                                 /* FIXME: use a dlist */
875                                                 e_dlist_remove((EDListNode *)pending);
876                                                 g_hash_table_remove(istore->pending_fetch_table, camel_message_info_uid(pending->info));
877                                                 camel_message_info_free(pending->info);
878                                                 /*e_memchunk_free(istore->pending_fetch_chunks, pending);*/
879                                         }
880                                 } else if (finfo->got & FETCH_FLAGS) {
881                                         if (pending == NULL) {
882                                                 pending = e_memchunk_alloc(istore->pending_fetch_chunks);
883                                                 pending->info = info;
884                                                 g_hash_table_insert(istore->pending_fetch_table, (char *)camel_message_info_uid(info), pending);
885                                                 e_dlist_addtail(&istore->pending_fetch_list, (EDListNode *)pending);
886                                         }
887                                 } else {
888                                         if (pending == NULL)
889                                                 camel_message_info_free(info);
890                                         printf("got unexpected fetch response?\n");
891                                         imap_dump_fetch(finfo);
892                                 }
893                         }
894                 }
895         } else {
896                 printf("unexpected fetch response, no folder selected?\n");
897         }
898         /*imap_dump_fetch(finfo);*/
899         imap_free_fetch(finfo);
900
901         return camel_imapp_engine_skip(ie);
902 }
903 #endif
904
905 /* ********************************************************************** */
906
907 /* should be moved to imapp-utils?
908    stuff in imapp-utils should be moved to imapp-parse? */
909
910 /* ********************************************************************** */
911
912 #if 0
913 void
914 camel_imapp_store_folder_selected(CamelIMAPPStore *store, CamelIMAPPFolder *folder, CamelIMAPPSelectResponse *select)
915 {
916         CamelIMAPPCommand * volatile ic = NULL;
917         CamelIMAPPStore *istore = (CamelIMAPPStore *)store;
918         int i;
919         struct _uidset_state ss;
920         GPtrArray *fetch;
921         CamelMessageInfo *info;
922         struct _pending_fetch *fw, *fn;
923
924         printf("imap folder selected\n");
925
926         if (select->uidvalidity == folder->uidvalidity
927             && select->exists == folder->exists
928             && select->recent == folder->recent
929             && select->unseen == folder->unseen) {
930                 /* no work to do? */
931                 return;
932         }
933
934         istore->pending_fetch_table = g_hash_table_new(g_str_hash, g_str_equal);
935         istore->pending_fetch_chunks = e_memchunk_new(256, sizeof(struct _pending_fetch));
936
937         /* perform an update - flags first (and see what we have) */
938         CAMEL_TRY {
939                 ic = camel_imapp_engine_command_new(istore->engine, "FETCH", NULL, "FETCH 1:%d (UID FLAGS)", select->exists);
940                 camel_imapp_engine_command_queue(istore->engine, ic);
941                 while (camel_imapp_engine_iterate(istore->engine, ic) > 0)
942                         ;
943
944                 if (ic->status->result != IMAP_OK)
945                         camel_exception_throw(1, "fetch failed: %s", ic->status->text);
946
947                 /* pending_fetch_list now contains any new messages */
948                 /* FIXME: how do we work out no-longer present messages? */
949                 printf("now fetching info for messages?\n");
950                 uidset_init(&ss, store->engine);
951                 ic = camel_imapp_engine_command_new(istore->engine, "FETCH", NULL, "UID FETCH ");
952                 fw = (struct _pending_fetch *)istore->pending_fetch_list.head;
953                 fn = fw->next;
954                 while (fn) {
955                         info = fw->info;
956                         /* if the uid set fills, then flush the command out */
957                         if (uidset_add(&ss, ic, camel_message_info_uid(info))
958                             || (fn->next == NULL && uidset_done(&ss, ic))) {
959                                 camel_imapp_engine_command_add(istore->engine, ic, " (FLAGS RFC822.HEADER)");
960                                 camel_imapp_engine_command_queue(istore->engine, ic);
961                                 while (camel_imapp_engine_iterate(istore->engine, ic) > 0)
962                                         ;
963                                 if (ic->status->result != IMAP_OK)
964                                         camel_exception_throw(1, "fetch failed: %s", ic->status->text);
965                                 /* if not end ... */
966                                 camel_imapp_engine_command_free(istore->engine, ic);
967                                 ic = camel_imapp_engine_command_new(istore->engine, "FETCH", NULL, "UID FETCH ");
968                         }
969                         fw = fn;
970                         fn = fn->next;
971                 }
972
973                 printf("The pending list should now be empty: %s\n", e_dlist_empty(&istore->pending_fetch_list)?"TRUE":"FALSE");
974                 for (i=0;i<10;i++) {
975                         info = camel_folder_summary_index(((CamelFolder *)istore->selected)->summary, i);
976                         if (info) {
977                                 printf("message info [%d] =\n", i);
978                                 camel_message_info_dump(info);
979                                 camel_message_info_free(info);
980                         }
981                 }
982         } CAMEL_CATCH (e) {
983                 /* FIXME: cleanup */
984                 camel_exception_throw_ex(e);
985         } CAMEL_DONE;
986
987         g_hash_table_destroy(istore->pending_fetch_table);
988         istore->pending_fetch_table = NULL;
989         e_memchunk_destroy(istore->pending_fetch_chunks);
990
991         camel_imapp_engine_command_free(istore->engine, ic);
992 }
993 #endif
994
995 #if 0
996 /*char *uids[] = {"1", "2", "4", "5", "6", "7", "9", "11", "12", 0};*/
997 /*char *uids[] = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", 0};*/
998 char *uids[] = {"1", "3", "5", "7", "9", "11", "12", "13", "14", "15", "20", "21", "24", "25", "26", 0};
999
1000 void
1001 uidset_test(CamelIMAPPEngine *ie)
1002 {
1003         struct _uidset_state ss;
1004         CamelIMAPPCommand *ic;
1005         int i;
1006
1007         /*ic = camel_imapp_engine_command_new(ie, 0, "FETCH", NULL, "FETCH ");*/
1008         uidset_init(&ss, 0, 0);
1009         for (i=0;uids[i];i++) {
1010                 if (uidset_add(&ss, uids[i])) {
1011                         printf("\n[%d] flushing uids\n", i);
1012                 }
1013         }
1014
1015         if (uidset_done(&ss)) {
1016                 printf("\nflushing uids\n");
1017         }
1018 }
1019 #endif