27c2d0ca64da970317470679ea2458b1cefb9ce4
[claws.git] / src / imap.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2001 Hiroyuki Yamamoto
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
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
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include <glib.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <stdlib.h>
28 #include <dirent.h>
29 #include <unistd.h>
30
31 #include "intl.h"
32 #include "imap.h"
33 #include "socket.h"
34 #include "recv.h"
35 #include "procmsg.h"
36 #include "procheader.h"
37 #include "folder.h"
38 #include "statusbar.h"
39 #include "prefs_account.h"
40 #include "codeconv.h"
41 #include "utils.h"
42
43 #define IMAP4_PORT      143
44
45 static GList *session_list = NULL;
46
47 static gint imap_cmd_count = 0;
48
49 static GSList *imap_get_uncached_messages       (IMAPSession    *session,
50                                                  FolderItem     *item,
51                                                  gint            first,
52                                                  gint            last);
53 static GSList *imap_delete_cached_messages      (GSList         *mlist,
54                                                  gint            first,
55                                                  gint            last);
56 static void imap_delete_all_cached_messages     (FolderItem     *item);
57
58 static SockInfo *imap_open      (const gchar    *server,
59                                  gushort         port,
60                                  gchar          *buf);
61 static gint imap_auth           (SockInfo       *sock,
62                                  const gchar    *user,
63                                  const gchar    *pass);
64 static gint imap_logout         (SockInfo       *sock);
65 static gint imap_noop           (SockInfo       *sock);
66 static gint imap_select         (SockInfo       *sock,
67                                  const gchar    *folder,
68                                  gint           *exists,
69                                  gint           *recent,
70                                  gint           *unseen,
71                                  gulong         *uid);
72 static gint imap_create         (SockInfo       *sock,
73                                  const gchar    *folder);
74 static gint imap_delete         (SockInfo       *sock,
75                                  const gchar    *folder);
76 static gint imap_get_envelope   (SockInfo       *sock,
77                                  gint            first,
78                                  gint            last);
79 #if 0
80 static gint imap_search         (SockInfo       *sock,
81                                  GSList         *numlist);
82 #endif
83 static gint imap_get_message    (SockInfo       *sock,
84                                  gint            num,
85                                  const gchar    *filename);
86 static gint imap_append_message (SockInfo       *sock,
87                                  const gchar    *destfolder,
88                                  const gchar    *file);
89 static gint imap_copy_message   (SockInfo       *sock,
90                                  gint            num,
91                                  const gchar    *destfolder);
92 static gint imap_store          (SockInfo       *sock,
93                                  gint            first,
94                                  gint            last,
95                                  gchar          *sub_cmd);
96
97 static gint imap_set_article_flags      (IMAPSession    *session,
98                                          gint            first,
99                                          gint            last,
100                                          IMAPFlags       flag,
101                                          gboolean        is_set);
102 static gint imap_expunge                (IMAPSession    *session);
103
104 static gchar *imap_parse_atom           (SockInfo *sock,
105                                          gchar    *src,
106                                          gchar    *dest,
107                                          gchar    *orig_buf);
108 static gchar *imap_parse_one_address    (SockInfo *sock,
109                                          gchar    *start,
110                                          gchar    *out_from_str,
111                                          gchar    *out_fromname_str,
112                                          gchar    *orig_buf);
113 static gchar *imap_parse_address        (SockInfo *sock,
114                                          gchar    *start,
115                                          gchar   **out_from_str,
116                                          gchar   **out_fromname_str,
117                                          gchar    *orig_buf);
118 static MsgFlags imap_parse_flags        (const gchar    *flag_str);
119 static MsgInfo *imap_parse_envelope     (SockInfo *sock,
120                                          gchar    *line_str);
121
122 static gint imap_ok             (SockInfo       *sock,
123                                  GPtrArray      *argbuf);
124 static void imap_gen_send       (SockInfo       *sock,
125                                  const gchar    *format, ...);
126 static gint imap_gen_recv       (SockInfo       *sock,
127                                  gchar          *buf,
128                                  gint            size);
129
130 static gchar *search_array_contain_str  (GPtrArray      *array,
131                                          gchar          *str);
132 static void imap_path_subst_slash_to_dot(gchar *str);
133
134
135 static IMAPSession *imap_session_connect_if_not(Folder *folder)
136 {
137         RemoteFolder *rfolder = REMOTE_FOLDER(folder);
138
139         g_return_val_if_fail(folder != NULL, NULL);
140         g_return_val_if_fail(folder->type == F_IMAP, NULL);
141         g_return_val_if_fail(folder->account != NULL, NULL);
142
143         if (!rfolder->session) {
144                 rfolder->session =
145                         imap_session_new(folder->account->recv_server,
146                                          IMAP4_PORT,
147                                          folder->account->userid,
148                                          folder->account->passwd);
149                 return IMAP_SESSION(rfolder->session);
150         }
151
152         if (imap_noop(rfolder->session->sock) != IMAP_SUCCESS) {
153                 log_warning(_("IMAP4 connection to %s:%d has been"
154                               " disconnected. Reconnecting...\n"),
155                             folder->account->recv_server, IMAP4_PORT);
156                 session_destroy(rfolder->session);
157                 rfolder->session =
158                         imap_session_new(folder->account->recv_server,
159                                          IMAP4_PORT, folder->account->userid,
160                                          folder->account->passwd);
161         }
162
163         statusbar_pop_all();
164
165         return IMAP_SESSION(rfolder->session);
166 }
167
168 Session *imap_session_new(const gchar *server, gushort port,
169                           const gchar *user, const gchar *pass)
170 {
171         gchar buf[IMAPBUFSIZE];
172         IMAPSession *session;
173         SockInfo *imap_sock;
174
175         g_return_val_if_fail(server != NULL, NULL);
176
177         log_message(_("creating IMAP4 connection to %s:%d ...\n"),
178                     server, port);
179
180         if ((imap_sock = imap_open(server, port, buf)) == NULL)
181                 return NULL;
182         if (imap_auth(imap_sock, user, pass) != IMAP_SUCCESS) {
183                 imap_logout(imap_sock);
184                 sock_close(imap_sock);
185                 return NULL;
186         }
187
188         session = g_new(IMAPSession, 1);
189         SESSION(session)->type      = SESSION_IMAP;
190         SESSION(session)->server    = g_strdup(server);
191         SESSION(session)->sock      = imap_sock;
192         SESSION(session)->connected = TRUE;
193         SESSION(session)->phase     = SESSION_READY;
194         SESSION(session)->data      = NULL;
195         session->mbox = NULL;
196
197         session_list = g_list_append(session_list, session);
198
199         return SESSION(session);
200 }
201
202 void imap_session_destroy(IMAPSession *session)
203 {
204         sock_close(SESSION(session)->sock);
205         SESSION(session)->sock = NULL;
206
207         g_free(session->mbox);
208
209         session_list = g_list_remove(session_list, session);
210 }
211
212 void imap_session_destroy_all(void)
213 {
214         while (session_list != NULL) {
215                 IMAPSession *session = (IMAPSession *)session_list->data;
216
217                 imap_logout(SESSION(session)->sock);
218                 imap_session_destroy(session);
219         }
220 }
221
222 GSList *imap_get_msg_list(Folder *folder, FolderItem *item, gboolean use_cache)
223 {
224         GSList *mlist = NULL;
225         IMAPSession *session;
226         gint ok, exists = 0, recent = 0, unseen = 0, begin = 1;
227         gulong uid = 0, last_uid;
228
229         g_return_val_if_fail(folder != NULL, NULL);
230         g_return_val_if_fail(item != NULL, NULL);
231         g_return_val_if_fail(folder->type == F_IMAP, NULL);
232         g_return_val_if_fail(folder->account != NULL, NULL);
233
234         session = imap_session_connect_if_not(folder);
235
236         if (!session) {
237                 mlist = procmsg_read_cache(item, FALSE);
238                 item->last_num = procmsg_get_last_num_in_cache(mlist);
239                 procmsg_set_flags(mlist, item);
240                 statusbar_pop_all();
241                 return mlist;
242         }
243
244         last_uid = item->mtime;
245
246         ok = imap_select(SESSION(session)->sock, item->path,
247                          &exists, &recent, &unseen, &uid);
248         if (ok != IMAP_SUCCESS) {
249                 log_warning(_("can't select folder: %s\n"), item->path);
250                 statusbar_pop_all();
251                 return NULL;
252         }
253
254         if (use_cache) {
255                 gint cache_last;
256
257                 mlist = procmsg_read_cache(item, FALSE);
258                 procmsg_set_flags(mlist, item);
259                 cache_last = procmsg_get_last_num_in_cache(mlist);
260
261                 /* calculating the range of envelope to get */
262                 if (exists < cache_last) {
263                         /* some messages are deleted (get all) */
264                         begin = 1;
265                 } else if (exists == cache_last) {
266                         if (last_uid != 0 && last_uid != uid) {
267                                 /* some recent but deleted (get all) */
268                                 begin = 1;
269                         } else {
270                                 /* mailbox unchanged (get none)*/
271                                 begin = -1;
272                         }
273                 } else {
274                         if (exists == cache_last + recent) {
275                                 /* some recent */
276                                 begin = cache_last + 1;
277                         } else {
278                                 /* some recent but deleted (get all) */
279                                 begin = 1;
280                         }
281                 }
282
283                 item->mtime = uid;
284         }
285
286         if (1 < begin && begin <= exists) {
287                 GSList *newlist;
288
289                 newlist = imap_get_uncached_messages
290                         (session, item, begin, exists);
291                 imap_delete_cached_messages(mlist, begin, INT_MAX);
292                 mlist = g_slist_concat(mlist, newlist);
293         } else if (begin == 1) {
294                 mlist = imap_get_uncached_messages(session, item, 1, exists);
295                 imap_delete_all_cached_messages(item);
296         }
297
298         item->last_num = exists;
299
300         statusbar_pop_all();
301
302         return mlist;
303 }
304
305 gchar *imap_fetch_msg(Folder *folder, FolderItem *item, gint num)
306 {
307         gchar *path, *filename;
308         IMAPSession *session;
309         gint ok;
310
311         g_return_val_if_fail(folder != NULL, NULL);
312         g_return_val_if_fail(item != NULL, NULL);
313
314         session = imap_session_connect_if_not(folder);
315
316         path = folder_item_get_path(item);
317         filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(num), NULL);
318         g_free(path);
319  
320         if (is_file_exist(filename)) {
321                 debug_print(_("message %d has been already cached.\n"), num);
322                 return filename;
323         }
324
325         if (!session) {
326                 g_free(filename);
327                 return NULL;
328         }
329
330         debug_print(_("getting message %d...\n"), num);
331         ok = imap_get_message(SESSION(session)->sock, num, filename);
332
333         statusbar_pop_all();
334
335         if (ok != IMAP_SUCCESS) {
336                 g_warning(_("can't fetch message %d\n"), num);
337                 g_free(filename);
338                 return NULL;
339         }
340
341         return filename;
342 }
343
344 gint imap_add_msg(Folder *folder, FolderItem *dest, const gchar *file,
345                   gboolean remove_source)
346 {
347         IMAPSession *session;
348         gint ok;
349
350         g_return_val_if_fail(folder != NULL, -1);
351         g_return_val_if_fail(dest != NULL, -1);
352         g_return_val_if_fail(file != NULL, -1);
353
354         session = imap_session_connect_if_not(folder);
355         if (!session)
356                 return -1;
357
358         if (dest->last_num < 0) {
359                 imap_scan_folder(folder, dest);
360                 if (dest->last_num < 0) return -1;
361         }
362
363         ok = imap_append_message(SESSION(session)->sock, dest->path, file);
364         if (ok != IMAP_SUCCESS) {
365                 g_warning(_("can't append message %s\n"), file);
366                 return -1;
367         }
368
369         if (remove_source) {
370                 if (unlink(file) < 0)
371                         FILE_OP_ERROR(file, "unlink");
372         }
373
374         return dest->last_num;
375 }
376
377 static gint imap_do_copy(Folder *folder, FolderItem *dest, MsgInfo *msginfo,
378                          gboolean remove_source)
379 {
380         gchar *destdir;
381         IMAPSession *session;
382
383         g_return_val_if_fail(folder != NULL, -1);
384         g_return_val_if_fail(dest != NULL, -1);
385         g_return_val_if_fail(msginfo != NULL, -1);
386
387         session = imap_session_connect_if_not(folder);
388         if (!session) return -1;
389
390         if (msginfo->folder == dest) {
391                 g_warning(_("the src folder is identical to the dest.\n"));
392                 return -1;
393         }
394
395         Xstrdup_a(destdir, dest->path, return -1);
396         /* imap_path_subst_slash_to_dot(destdir); */
397
398         debug_print(_("%s message %s%c%d to %s ...\n"),
399                     remove_source ? "Moving" : "Copying",
400                     msginfo->folder->path, G_DIR_SEPARATOR,
401                     msginfo->msgnum, destdir);
402
403         imap_copy_message(SESSION(session)->sock, msginfo->msgnum, destdir);
404
405         if (remove_source) {
406                 imap_set_article_flags(session, msginfo->msgnum, msginfo->msgnum,
407                                        IMAP_FLAG_DELETED, TRUE);
408                 imap_expunge(session);
409         }
410
411         return IMAP_SUCCESS;
412 }
413
414 gint imap_do_copy_msgs_with_dest(Folder *folder, FolderItem *dest,
415                                  GSList *msglist, gboolean remove_source)
416 {
417         gchar *destdir;
418         GSList *cur;
419         MsgInfo *msginfo;
420         IMAPSession *session;
421
422         g_return_val_if_fail(folder != NULL, -1);
423         g_return_val_if_fail(dest != NULL, -1);
424         g_return_val_if_fail(msglist != NULL, -1);
425
426         session = imap_session_connect_if_not(folder);
427         if (!session) return -1;
428
429         Xstrdup_a(destdir, dest->path, return -1);
430         /* imap_path_subst_slash_to_dot(destdir); */
431
432         for (cur = msglist; cur != NULL; cur = cur->next) {
433                 msginfo = (MsgInfo *)cur->data;
434
435                 if (msginfo->folder == dest) {
436                         g_warning(_("the src folder is identical to the dest.\n"));
437                         continue;
438                 }
439
440                 debug_print(_("%s message %s%c%d to %s ...\n"),
441                             remove_source ? "Moving" : "Copying",
442                             msginfo->folder->path, G_DIR_SEPARATOR,
443                             msginfo->msgnum, destdir);
444
445                 imap_copy_message(SESSION(session)->sock,
446                                   msginfo->msgnum, destdir);
447
448                 if (remove_source) {
449                         imap_set_article_flags
450                                 (session, msginfo->msgnum, msginfo->msgnum,
451                                  IMAP_FLAG_DELETED, TRUE);
452                 }
453         }
454
455         if (remove_source)
456                 imap_expunge(session);
457
458         return IMAP_SUCCESS;
459
460 }
461
462 gint imap_move_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
463 {
464         return imap_do_copy(folder, dest, msginfo, TRUE);
465 }
466
467 gint imap_move_msgs_with_dest(Folder *folder, FolderItem *dest,
468                               GSList *msglist)
469 {
470         return imap_do_copy_msgs_with_dest(folder, dest, msglist, TRUE);
471 }
472
473 gint imap_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
474 {
475         return imap_do_copy(folder, dest, msginfo, FALSE);
476 }
477
478 gint imap_copy_msgs_with_dest(Folder *folder, FolderItem *dest,
479                               GSList *msglist)
480 {
481         return imap_do_copy_msgs_with_dest(folder, dest, msglist, FALSE);
482 }
483
484 gint imap_remove_msg(Folder *folder, FolderItem *item, gint num)
485 {
486         gint exists, recent, unseen;
487         gulong uid;
488         gint ok;
489         IMAPSession *session;
490
491         g_return_val_if_fail(folder != NULL, -1);
492         g_return_val_if_fail(item != NULL, -1);
493         g_return_val_if_fail(num > 0 && num <= item->last_num, -1);
494
495         session = imap_session_connect_if_not(folder);
496         if (!session) return -1;
497
498         ok = imap_select(SESSION(session)->sock, item->path,
499                          &exists, &recent, &unseen, &uid);
500         if (ok != IMAP_SUCCESS) {
501                 log_warning(_("can't select folder: %s\n"), item->path);
502                 return ok;
503         }
504
505         ok = imap_set_article_flags(IMAP_SESSION(REMOTE_FOLDER(folder)->session),
506                                     num, num, IMAP_FLAG_DELETED, TRUE);
507         if (ok != IMAP_SUCCESS) {
508                 log_warning(_("can't set deleted flags: %d\n"), num);
509                 return ok;
510         }
511
512         ok = imap_expunge(session);
513         if (ok != IMAP_SUCCESS) {
514                 log_warning(_("can't expunge\n"));
515                 return ok;
516         }
517
518         return IMAP_SUCCESS;
519 }
520
521 gint imap_remove_all_msg(Folder *folder, FolderItem *item)
522 {
523         gint exists, recent, unseen;
524         gulong uid;
525         gint ok;
526         IMAPSession *session;
527
528         g_return_val_if_fail(folder != NULL, -1);
529         g_return_val_if_fail(item != NULL, -1);
530
531         session = imap_session_connect_if_not(folder);
532         if (!session) return -1;
533
534         ok = imap_select(SESSION(session)->sock, item->path,
535                          &exists, &recent, &unseen, &uid);
536         if (ok != IMAP_SUCCESS) {
537                 log_warning(_("can't select folder: %s\n"), item->path);
538                 return ok;
539         }
540
541         ok = imap_set_article_flags(session, 1, exists,
542                                     IMAP_FLAG_DELETED, TRUE);
543         if (ok != IMAP_SUCCESS) {
544                 log_warning(_("can't set deleted flags: 1:%d\n"), exists);
545                 return ok;
546         }
547
548         ok = imap_expunge(session);
549         if (ok != IMAP_SUCCESS) {
550                 log_warning(_("can't expunge\n"));
551                 return ok;
552         }
553
554         return IMAP_SUCCESS;
555 }
556
557 void imap_scan_folder(Folder *folder, FolderItem *item)
558 {
559 }
560
561 FolderItem *imap_create_folder(Folder *folder, FolderItem *parent,
562                                const gchar *name)
563 {
564         gchar *dirpath, *imappath;
565         IMAPSession *session;
566         FolderItem *new_item;
567
568         g_return_val_if_fail(folder != NULL, NULL);
569         g_return_val_if_fail(parent != NULL, NULL);
570         g_return_val_if_fail(name != NULL, NULL);
571
572         session = imap_session_connect_if_not(folder);
573         if (!session) return NULL;
574
575         if (parent->path)
576                 dirpath = g_strconcat(parent->path, G_DIR_SEPARATOR_S, name,
577                                       NULL);
578         else
579                 dirpath = g_strdup(name);
580
581         imappath = g_strdup(dirpath);
582         /* imap_path_subst_slash_to_dot(imappath); */
583
584         if (strcasecmp(name, "INBOX") != 0) {
585                 gint ok;
586
587                 ok = imap_create(SESSION(session)->sock, imappath);
588                 if (ok != IMAP_SUCCESS) {
589                         log_warning(_("can't create mailbox\n"));
590                         g_free(imappath);
591                         g_free(dirpath);
592                         return NULL;
593                 }
594         }
595
596         new_item = folder_item_new(name, dirpath);
597         folder_item_append(parent, new_item);
598         g_free(imappath);
599         g_free(dirpath);
600
601         return new_item;
602 }
603
604 gint imap_remove_folder(Folder *folder, FolderItem *item)
605 {
606         gint ok;
607         IMAPSession *session;
608
609         g_return_val_if_fail(folder != NULL, -1);
610         g_return_val_if_fail(item != NULL, -1);
611         g_return_val_if_fail(item->path != NULL, -1);
612
613         session = imap_session_connect_if_not(folder);
614         if (!session) return -1;
615
616         ok = imap_delete(SESSION(session)->sock, item->path);
617         if (ok != IMAP_SUCCESS) {
618                 log_warning(_("can't delete mailbox\n"));
619                 return -1;
620         }
621
622         folder_item_remove(item);
623
624         return 0;
625 }
626
627 static GSList *imap_get_uncached_messages(IMAPSession *session,
628                                           FolderItem *item,
629                                           gint first, gint last)
630 {
631         gchar buf[IMAPBUFSIZE];
632         GSList *newlist = NULL;
633         GSList *llast = NULL;
634         MsgInfo *msginfo;
635
636         g_return_val_if_fail(session != NULL, NULL);
637         g_return_val_if_fail(item != NULL, NULL);
638         g_return_val_if_fail(item->folder != NULL, NULL);
639         g_return_val_if_fail(item->folder->type == F_IMAP, NULL);
640         g_return_val_if_fail(first <= last, NULL);
641
642         if (imap_get_envelope(SESSION(session)->sock, first, last)
643             != IMAP_SUCCESS) {
644                 log_warning(_("can't get envelope\n"));
645                 return NULL;
646         }
647
648         for (;;) {
649                 if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
650                         log_warning(_("error occurred while getting envelope.\n"));
651                         return newlist;
652                 }
653                 strretchomp(buf);
654                 if (buf[0] != '*' || buf[1] != ' ') break;
655
656                 msginfo = imap_parse_envelope(SESSION(session)->sock, buf);
657                 if (!msginfo) {
658                         log_warning(_("can't parse envelope: %s\n"), buf);
659                         continue;
660                 }
661
662                 msginfo->folder = item;
663
664                 if (!newlist)
665                         llast = newlist = g_slist_append(newlist, msginfo);
666                 else {
667                         llast = g_slist_append(llast, msginfo);
668                         llast = llast->next;
669                 }
670         }
671
672         return newlist;
673 }
674
675 static GSList *imap_delete_cached_messages(GSList *mlist,
676                                            gint first, gint last)
677 {
678         GSList *cur, *next;
679         MsgInfo *msginfo;
680         gchar *cache_file;
681
682         for (cur = mlist; cur != NULL; ) {
683                 next = cur->next;
684
685                 msginfo = (MsgInfo *)cur->data;
686                 if (msginfo != NULL && first <= msginfo->msgnum &&
687                     msginfo->msgnum <= last) {
688                         debug_print(_("deleting message %d...\n"),
689                                     msginfo->msgnum);
690
691                         cache_file = procmsg_get_message_file_path(msginfo);
692                         if (is_file_exist(cache_file)) unlink(cache_file);
693                         g_free(cache_file);
694
695                         procmsg_msginfo_free(msginfo);
696                         mlist = g_slist_remove(mlist, msginfo);
697                 }
698
699                 cur = next;
700         }
701
702         return mlist;
703 }
704
705 static void imap_delete_all_cached_messages(FolderItem *item)
706 {
707         gchar *dir;
708
709         g_return_if_fail(item != NULL);
710         g_return_if_fail(item->folder != NULL);
711         g_return_if_fail(item->folder->type == F_IMAP);
712
713         debug_print(_("\tDeleting all cached messages... "));
714
715         dir = folder_item_get_path(item);
716         remove_all_numbered_files(dir);
717         g_free(dir);
718
719         debug_print(_("done.\n"));
720 }
721
722 static SockInfo *imap_open(const gchar *server, gushort port, gchar *buf)
723 {
724         SockInfo *sock;
725
726         if ((sock = sock_connect(server, port)) == NULL) {
727                 log_warning(_("Can't connect to IMAP4 server: %s:%d\n"),
728                             server, port);
729                 return NULL;
730         }
731
732         imap_cmd_count = 0;
733
734         if (imap_noop(sock) != IMAP_SUCCESS) {
735                 sock_close(sock);
736                 return NULL;
737         }
738
739         return sock;
740 }
741
742 static gint imap_auth(SockInfo *sock, const gchar *user, const gchar *pass)
743 {
744         gint ok;
745         GPtrArray *argbuf;
746
747         imap_gen_send(sock, "LOGIN \"%s\" %s", user, pass);
748         argbuf = g_ptr_array_new();
749         ok = imap_ok(sock, argbuf);
750         if (ok != IMAP_SUCCESS) log_warning(_("IMAP4 login failed.\n"));
751         ptr_array_free_strings(argbuf);
752         g_ptr_array_free(argbuf, TRUE);
753
754         return ok;
755 }
756
757 static gint imap_logout(SockInfo *sock)
758 {
759         imap_gen_send(sock, "LOGOUT");
760         return imap_ok(sock, NULL);
761 }
762
763 static gint imap_noop(SockInfo *sock)
764 {
765         imap_gen_send(sock, "NOOP");
766         return imap_ok(sock, NULL);
767 }
768
769 static gint imap_select(SockInfo *sock, const gchar *folder,
770                         gint *exists, gint *recent, gint *unseen, gulong *uid)
771 {
772         gint ok;
773         gchar *resp_str;
774         GPtrArray *argbuf;
775         gchar *imappath;
776
777         *exists = *recent = *unseen = *uid = 0;
778         argbuf = g_ptr_array_new();
779
780         Xstrdup_a(imappath, folder, return -1);
781         /* imap_path_subst_slash_to_dot(imappath); */
782
783         imap_gen_send(sock, "SELECT \"%s\"", imappath);
784         if ((ok = imap_ok(sock, argbuf)) != IMAP_SUCCESS)
785                 goto bail;
786
787         resp_str = search_array_contain_str(argbuf, "EXISTS");
788         if (resp_str) {
789                 if (sscanf(resp_str,"%d EXISTS", exists) != 1) {
790                         g_warning("imap_select(): invalid EXISTS line.\n");
791                         goto bail;
792                 }
793         }
794
795         resp_str = search_array_contain_str(argbuf, "RECENT");
796         if (resp_str) {
797                 if (sscanf(resp_str, "%d RECENT", recent) != 1) {
798                         g_warning("imap_select(): invalid RECENT line.\n");
799                         goto bail;
800                 }
801         }
802
803         resp_str = search_array_contain_str(argbuf, "UIDVALIDITY");
804         if (resp_str) {
805                 if (sscanf(resp_str, "OK [UIDVALIDITY %lu] ", uid) != 1) {
806                         g_warning("imap_select(): invalid UIDVALIDITY line.\n");
807                         goto bail;
808                 }
809         }
810
811         resp_str = search_array_contain_str(argbuf, "UNSEEN");
812         if (resp_str) {
813                 if (sscanf(resp_str, "OK [UNSEEN %d] ", unseen) != 1) {
814                         g_warning("imap_select(): invalid UNSEEN line.\n");
815                         goto bail;
816                 }
817         }
818
819 bail:
820         ptr_array_free_strings(argbuf);
821         g_ptr_array_free(argbuf, TRUE);
822
823         return ok;
824 }
825
826 static gint imap_create(SockInfo *sock, const gchar *folder)
827 {
828         imap_gen_send(sock, "CREATE \"%s\"", folder);
829         return imap_ok(sock, NULL);
830 }
831
832 static gint imap_delete(SockInfo *sock, const gchar *folder)
833 {
834         imap_gen_send(sock, "DELETE \"%s\"", folder);
835         return imap_ok(sock, NULL);
836 }
837
838 static gchar *strchr_cpy(const gchar *src, gchar ch, gchar *dest, gint len)
839 {
840         gchar *tmp;
841
842         dest[0] = '\0';
843         tmp = strchr(src, ch);
844         if (!tmp || tmp == src)
845                 return NULL;
846
847         memcpy(dest, src, MIN(tmp - src, len - 1));
848         dest[MIN(tmp - src, len - 1)] = '\0';
849
850         return tmp + 1;
851 }
852
853 static gint imap_get_message(SockInfo *sock, gint num, const gchar *filename)
854 {
855         gint ok;
856         gchar buf[IMAPBUFSIZE];
857         gchar *cur_pos;
858         gchar size_str[32];
859         glong size_num;
860
861         g_return_val_if_fail(filename != NULL, IMAP_ERROR);
862
863         imap_gen_send(sock, "FETCH %d BODY[]", num);
864
865         if (sock_gets(sock, buf, sizeof(buf)) < 0)
866                 return IMAP_ERROR;
867         strretchomp(buf);
868         if (buf[0] != '*' || buf[1] != ' ')
869                 return IMAP_ERROR;
870         log_print("IMAP4< %s\n", buf);
871
872         cur_pos = strchr(buf, '{');
873         g_return_val_if_fail(cur_pos != NULL, IMAP_ERROR);
874         cur_pos = strchr_cpy(cur_pos + 1, '}', size_str, sizeof(size_str));
875         g_return_val_if_fail(cur_pos != NULL, IMAP_ERROR);
876         size_num = atol(size_str);
877
878         if (*cur_pos != '\0') return IMAP_ERROR;
879
880         if (recv_bytes_write_to_file(sock, size_num, filename) != 0)
881                 return IMAP_ERROR;
882
883         if (imap_gen_recv(sock, buf, sizeof(buf)) != IMAP_SUCCESS)
884                 return IMAP_ERROR;
885
886         if (buf[0] != ')')
887                 return IMAP_ERROR;
888
889         ok = imap_ok(sock, NULL);
890
891         return ok;
892 }
893
894 static gint imap_append_message(SockInfo *sock, const gchar *destfolder,
895                                 const gchar *file)
896 {
897         gint ok;
898         gint size;
899
900         g_return_val_if_fail(file != NULL, IMAP_ERROR);
901
902         imap_gen_send(sock, "APPEND %s () {%d}", destfolder, size);
903         ok = imap_ok(sock, NULL);
904         if (ok != IMAP_SUCCESS) {
905                 log_warning(_("can't append %s to %s\n"), file, destfolder);
906                 return -1;
907         }
908
909         return ok;
910 }
911
912 static gint imap_copy_message(SockInfo *sock, gint num, const gchar *destfolder)
913 {
914         gint ok;
915
916         g_return_val_if_fail(destfolder != NULL, IMAP_ERROR);
917
918         imap_gen_send(sock, "COPY %d %s", num, destfolder);
919         ok = imap_ok(sock, NULL);
920         if (ok != IMAP_SUCCESS) {
921                 log_warning(_("can't copy %d to %s\n"), num, destfolder);
922                 return -1;
923         }
924
925         return ok;
926 }
927
928 static gchar *imap_parse_atom(SockInfo *sock, gchar *src, gchar *dest,
929                               gchar *orig_buf)
930 {
931         gchar *cur_pos = src;
932
933         while (*cur_pos == ' ') cur_pos++;
934
935         if (!strncmp(cur_pos, "NIL", 3)) {
936                 *dest = '\0';
937                 cur_pos += 3;
938         } else if (*cur_pos == '\"') {
939                 gchar *p;
940
941                 p = strchr_cpy(cur_pos + 1, '\"', dest, IMAPBUFSIZE);
942                 cur_pos = p ? p : cur_pos + 2;
943         } else if (*cur_pos == '{') {
944                 gchar buf[32];
945                 gint len;
946
947                 cur_pos = strchr_cpy(cur_pos + 1, '}', buf, sizeof(buf));
948                 len = atoi(buf);
949
950                 g_return_val_if_fail(orig_buf != NULL, cur_pos);
951
952                 if (sock_gets(sock, orig_buf, IMAPBUFSIZE) < 0)
953                         return cur_pos;
954                 strretchomp(orig_buf);
955                 log_print("IMAP4< %s\n", orig_buf);
956                 memcpy(dest, orig_buf, len);
957                 dest[len] = '\0';
958                 cur_pos = orig_buf + len;
959         }
960
961         return cur_pos;
962 }
963
964 static gchar *imap_parse_one_address(SockInfo *sock, gchar *start,
965                                      gchar *out_from_str,
966                                      gchar *out_fromname_str,
967                                      gchar *orig_buf)
968 {
969         gchar buf[IMAPBUFSIZE];
970         gchar *cur_pos = start;
971
972         cur_pos = imap_parse_atom(sock, cur_pos, buf, orig_buf);
973         conv_unmime_header(out_fromname_str, 256, buf, NULL);
974
975         if (out_fromname_str[0] != '\0') {
976                 strcat(out_from_str, "\"");
977                 strcat(out_from_str, out_fromname_str);
978                 strcat(out_from_str, "\"");
979         }
980
981         cur_pos = imap_parse_atom(sock, cur_pos, buf, orig_buf);
982
983         strcat(out_from_str, " <");
984
985         cur_pos = imap_parse_atom(sock, cur_pos, buf, orig_buf);
986         strcat(out_from_str, buf);
987
988         cur_pos = imap_parse_atom(sock, cur_pos, buf, orig_buf);
989         strcat(out_from_str, "@");
990         strcat(out_from_str, buf);
991         strcat(out_from_str, ">");
992
993         while (*cur_pos == ' ') cur_pos++;
994
995         g_return_val_if_fail(*cur_pos == ')', cur_pos + 1);
996
997         return cur_pos + 1;
998 }
999
1000 static gchar *imap_parse_address(SockInfo *sock, gchar *start,
1001                                  gchar **out_from_str,
1002                                  gchar **out_fromname_str,
1003                                  gchar *orig_buf)
1004 {
1005         gchar buf[IMAPBUFSIZE];
1006         gchar name_buf[IMAPBUFSIZE];
1007         gchar *cur_pos = start;
1008         gboolean first = TRUE;
1009
1010         if (out_from_str)     *out_from_str     = NULL;
1011         if (out_fromname_str) *out_fromname_str = NULL;
1012         buf[0] = name_buf[0] = '\0';
1013
1014         if (!strncmp(cur_pos, "NIL", 3)) {
1015                 if (out_from_str)     *out_from_str     = g_strdup("");
1016                 if (out_fromname_str) *out_fromname_str = g_strdup("");
1017                 return cur_pos + 3;
1018         }
1019
1020         g_return_val_if_fail(*cur_pos == '(', NULL);
1021         cur_pos++;
1022
1023         for (;;) {
1024                 gchar ch = *cur_pos++;
1025                 if (ch == ')') break;
1026                 if (ch == '(') {
1027                         if (!first) strcat(buf, ", ");
1028                         first = FALSE;
1029                         cur_pos = imap_parse_one_address
1030                                 (sock, cur_pos, buf, name_buf, orig_buf);
1031                         if (!cur_pos) return NULL;
1032                 }
1033         }
1034
1035         if (out_from_str)     *out_from_str     = g_strdup(buf);
1036         if (out_fromname_str) *out_fromname_str = g_strdup(name_buf);
1037
1038         return cur_pos;
1039 }
1040
1041 static MsgFlags imap_parse_flags(const gchar *flag_str)  
1042 {
1043         gchar buf[32];
1044         const gchar *cur_pos = flag_str;
1045         const gchar *last_pos;
1046         MsgFlags flags;
1047
1048         flags = 0;
1049         MSG_SET_FLAGS(flags, MSG_UNREAD|MSG_IMAP);
1050
1051         while (cur_pos != NULL) {
1052                 cur_pos = strchr(cur_pos, '\\');
1053                 if (cur_pos == NULL) break;
1054
1055                 last_pos = cur_pos + 1;
1056                 cur_pos = strchr_cpy(last_pos, ' ', buf, sizeof(buf));
1057                 if (cur_pos == NULL)
1058                         strncpy2(buf, last_pos, sizeof(buf));
1059
1060                 if (g_strcasecmp(buf, "Recent") == 0) {
1061                         MSG_SET_FLAGS(flags, MSG_NEW|MSG_UNREAD);
1062                 } else if (g_strcasecmp(buf, "Seen") == 0) {
1063                         MSG_UNSET_FLAGS(flags, MSG_NEW|MSG_UNREAD);
1064                 } else if (g_strcasecmp(buf, "Deleted") == 0) {
1065                         MSG_SET_FLAGS(flags, MSG_DELETED);
1066                 } else if (g_strcasecmp(buf, "Flagged") == 0) {
1067                         MSG_SET_FLAGS(flags, MSG_MARKED);
1068                 }
1069         }
1070
1071         return flags;
1072 }
1073
1074 static MsgInfo *imap_parse_envelope(SockInfo *sock, gchar *line_str)
1075 {
1076         MsgInfo *msginfo;
1077         gchar buf[IMAPBUFSIZE];
1078         gchar tmp[IMAPBUFSIZE];
1079         gchar *cur_pos;
1080         gint msgnum;
1081         size_t size;
1082         gchar *date = NULL;
1083         time_t date_t;
1084         gchar *subject = NULL;
1085         gchar *tmp_from;
1086         gchar *tmp_fromname;
1087         gchar *from = NULL;
1088         gchar *fromname = NULL;
1089         gchar *tmp_to;
1090         gchar *to = NULL;
1091         gchar *inreplyto = NULL;
1092         gchar *msgid = NULL;
1093         MsgFlags flags;
1094
1095         log_print("IMAP4< %s\n", line_str);
1096
1097         g_return_val_if_fail(line_str != NULL, NULL);
1098         g_return_val_if_fail(line_str[0] == '*' && line_str[1] == ' ', NULL);
1099
1100         cur_pos = line_str + 2;
1101
1102 #define PARSE_ONE_ELEMENT(ch) \
1103 { \
1104         cur_pos = strchr_cpy(cur_pos, ch, buf, sizeof(buf)); \
1105         g_return_val_if_fail(cur_pos != NULL, NULL); \
1106 }
1107
1108         PARSE_ONE_ELEMENT(' ');
1109         msgnum = atoi(buf);
1110
1111         PARSE_ONE_ELEMENT(' ');
1112         g_return_val_if_fail(!strcmp(buf, "FETCH"), NULL);
1113
1114         PARSE_ONE_ELEMENT(' ');
1115         g_return_val_if_fail(!strcmp(buf, "(FLAGS"), NULL);
1116
1117         PARSE_ONE_ELEMENT(')');
1118         g_return_val_if_fail(*buf == '(', NULL);
1119         flags = imap_parse_flags(buf + 1);
1120
1121         g_return_val_if_fail(*cur_pos == ' ', NULL);
1122         g_return_val_if_fail
1123                 ((cur_pos = strchr_cpy(cur_pos + 1, ' ', buf, sizeof(buf))),
1124                  NULL);
1125         g_return_val_if_fail(!strcmp(buf, "RFC822.SIZE"), NULL);
1126
1127         PARSE_ONE_ELEMENT(' ');
1128         size = atoi(buf);
1129
1130         PARSE_ONE_ELEMENT(' ');
1131         g_return_val_if_fail(!strcmp(buf, "ENVELOPE"), NULL);
1132
1133         g_return_val_if_fail(*cur_pos == '(', NULL);
1134         cur_pos = imap_parse_atom(sock, cur_pos + 1, buf, line_str);
1135         Xstrdup_a(date, buf, return NULL);
1136         date_t = procheader_date_parse(NULL, date, 0);
1137
1138         cur_pos = imap_parse_atom(sock, cur_pos, buf, line_str);
1139         if (buf[0] != '\0') {
1140                 conv_unmime_header(tmp, sizeof(tmp), buf, NULL);
1141                 Xstrdup_a(subject, tmp, return NULL);
1142         }
1143
1144         g_return_val_if_fail(*cur_pos == ' ', NULL);
1145         cur_pos = imap_parse_address(sock, cur_pos + 1,
1146                                      &tmp_from, &tmp_fromname, line_str);
1147         Xstrdup_a(from, tmp_from,
1148                   {g_free(tmp_from); g_free(tmp_fromname); return NULL;});
1149         Xstrdup_a(fromname, tmp_fromname,
1150                   {g_free(tmp_from); g_free(tmp_fromname); return NULL;});
1151         g_free(tmp_from);
1152         g_free(tmp_fromname);
1153
1154 #define SKIP_ONE_ELEMENT() \
1155 { \
1156         g_return_val_if_fail(*cur_pos == ' ', NULL); \
1157         cur_pos = imap_parse_address(sock, cur_pos + 1, \
1158                                      NULL, NULL, line_str); \
1159 }
1160
1161         /* skip sender and reply-to */
1162         SKIP_ONE_ELEMENT();
1163         SKIP_ONE_ELEMENT();
1164
1165         g_return_val_if_fail(*cur_pos == ' ', NULL);
1166         cur_pos = imap_parse_address(sock, cur_pos + 1, &tmp_to, NULL, line_str);
1167         Xstrdup_a(to, tmp_to, {g_free(tmp_to); return NULL;});
1168         g_free(tmp_to);
1169
1170         /* skip Cc and Bcc */
1171         SKIP_ONE_ELEMENT();
1172         SKIP_ONE_ELEMENT();
1173
1174 #undef SKIP_ONE_ELEMENT
1175
1176         g_return_val_if_fail(*cur_pos == ' ', NULL);
1177         cur_pos = imap_parse_atom(sock, cur_pos, buf, line_str);
1178         if (buf[0] != '\0') {
1179                 eliminate_parenthesis(buf, '(', ')');
1180                 extract_parenthesis(buf, '<', '>');
1181                 remove_space(buf);
1182                 Xstrdup_a(inreplyto, buf, return NULL);
1183         }
1184
1185         g_return_val_if_fail(*cur_pos == ' ', NULL);
1186         cur_pos = imap_parse_atom(sock, cur_pos, buf, line_str);
1187         if (buf[0] != '\0') {
1188                 extract_parenthesis(buf, '<', '>');
1189                 remove_space(buf);
1190                 Xstrdup_a(msgid, buf, return NULL);
1191         }
1192
1193         msginfo = g_new0(MsgInfo, 1);
1194         msginfo->msgnum = msgnum;
1195         msginfo->size = size;
1196         msginfo->date = g_strdup(date);
1197         msginfo->date_t = date_t;
1198         msginfo->subject = g_strdup(subject);
1199         msginfo->from = g_strdup(from);
1200         msginfo->fromname = g_strdup(fromname);
1201         msginfo->to = g_strdup(to);
1202         msginfo->inreplyto = g_strdup(inreplyto);
1203         msginfo->msgid = g_strdup(msgid);
1204         msginfo->flags = flags;
1205
1206         return msginfo;
1207 }
1208
1209 gint imap_get_envelope(SockInfo *sock, gint first, gint last)
1210 {
1211         imap_gen_send(sock, "FETCH %d:%d (FLAGS RFC822.SIZE ENVELOPE)",
1212                       first, last);
1213
1214         return IMAP_SUCCESS;
1215 }
1216
1217 static gint imap_store(SockInfo *sock, gint first, gint last, gchar *sub_cmd)
1218 {
1219         gint ok;
1220         GPtrArray *argbuf;
1221
1222         argbuf = g_ptr_array_new();
1223
1224         imap_gen_send(sock, "STORE %d:%d %s", first, last, sub_cmd);
1225         
1226         if ((ok = imap_ok(sock, argbuf)) != IMAP_SUCCESS) {
1227                 g_ptr_array_free(argbuf, TRUE);
1228                 log_warning(_("error while imap command: STORE %d:%d %s\n"),
1229                             first, last, sub_cmd);
1230                 return ok;
1231         }
1232
1233         g_ptr_array_free(argbuf, TRUE);
1234
1235         return IMAP_SUCCESS;
1236 }
1237
1238 static gint imap_set_article_flags(IMAPSession *session,
1239                                    gint first,
1240                                    gint last,
1241                                    IMAPFlags flags,
1242                                    gboolean is_set)
1243 {
1244         GString *buf;
1245         gint ok;
1246
1247         buf = g_string_new(is_set ? "+FLAGS (" : "-FLAGS (");
1248
1249         if (IMAP_IS_SEEN(flags))        g_string_append(buf, "\\Seen ");
1250         if (IMAP_IS_ANSWERED(flags))    g_string_append(buf, "\\Answered ");
1251         if (IMAP_IS_FLAGGED(flags))     g_string_append(buf, "\\Flagged ");
1252         if (IMAP_IS_DELETED(flags))     g_string_append(buf, "\\Deleted ");
1253         if (IMAP_IS_DRAFT(flags))       g_string_append(buf, "\\Draft");
1254
1255         if (buf->str[buf->len - 1] == ' ')
1256                 g_string_truncate(buf, buf->len - 1);
1257
1258         g_string_append_c(buf, ')');
1259
1260         ok = imap_store(SESSION(session)->sock, first, last, buf->str);
1261         g_string_free(buf, TRUE);
1262
1263         return ok;
1264 }
1265
1266 static gint imap_expunge(IMAPSession *session)
1267 {
1268         gint ok;
1269         GPtrArray *argbuf;
1270
1271         argbuf = g_ptr_array_new();
1272
1273         imap_gen_send(SESSION(session)->sock, "EXPUNGE");
1274
1275         if ((ok = imap_ok(SESSION(session)->sock, argbuf)) != IMAP_SUCCESS) {
1276                 log_warning(_("error while imap command: EXPUNGE\n"));
1277                 g_ptr_array_free(argbuf, TRUE);
1278                 return ok;
1279         }
1280
1281         g_ptr_array_free(argbuf, TRUE);
1282
1283         return IMAP_SUCCESS;
1284 }
1285
1286 static gint imap_ok(SockInfo *sock, GPtrArray *argbuf)
1287 {
1288         gint ok;
1289         gchar buf[IMAPBUFSIZE];
1290         gint cmd_num;
1291         gchar cmd_status[IMAPBUFSIZE];
1292
1293         while ((ok = imap_gen_recv(sock, buf, sizeof(buf))) == IMAP_SUCCESS) {
1294                 if (buf[0] == '*' && buf[1] == ' ') {
1295                         if (argbuf)
1296                                 g_ptr_array_add(argbuf, g_strdup(&buf[2]));
1297                 } else {
1298                         if (sscanf(buf, "%d %s", &cmd_num, cmd_status) < 2)
1299                                 return IMAP_ERROR;
1300                         else if (cmd_num == imap_cmd_count &&
1301                                  !strcmp(cmd_status, "OK")) {
1302                                 if (argbuf)
1303                                         g_ptr_array_add(argbuf, g_strdup(buf));
1304                                 return IMAP_SUCCESS;
1305                         } else
1306                                 return IMAP_ERROR;
1307                 }
1308         }
1309
1310         return ok;
1311 }
1312
1313 static gchar *search_array_contain_str(GPtrArray *array, gchar *str)
1314 {
1315         gint i;
1316
1317         for (i = 0; i < array->len; i++) {
1318                 gchar *tmp;
1319
1320                 tmp = g_ptr_array_index(array, i);
1321                 if (strstr(tmp, str) != NULL)
1322                         return tmp;
1323         }
1324
1325         return NULL;
1326 }
1327
1328 static void imap_gen_send(SockInfo *sock, const gchar *format, ...)
1329 {
1330         gchar buf[IMAPBUFSIZE];
1331         gchar tmp[IMAPBUFSIZE];
1332         gchar *p;
1333         va_list args;
1334
1335         va_start(args, format);
1336         g_vsnprintf(tmp, sizeof(tmp), format, args);
1337         va_end(args);
1338
1339         imap_cmd_count++;
1340
1341         g_snprintf(buf, sizeof(buf), "%d %s\r\n", imap_cmd_count, tmp);
1342         if (!strncasecmp(tmp, "LOGIN ", 6) && (p = strchr(tmp + 6, ' '))) {
1343                 *(p + 1) = '\0';
1344                 log_print("IMAP4> %d %s ********\n", imap_cmd_count, tmp);
1345         } else
1346                 log_print("IMAP4> %d %s\n", imap_cmd_count, tmp);
1347
1348         sock_write(sock, buf, strlen(buf));
1349 }
1350
1351 static gint imap_gen_recv(SockInfo *sock, gchar *buf, gint size)
1352 {
1353         if (sock_gets(sock, buf, size) == -1)
1354                 return IMAP_SOCKET;
1355
1356         strretchomp(buf);
1357
1358         log_print("IMAP4< %s\n", buf);
1359
1360         return IMAP_SUCCESS;
1361 }
1362
1363 static void imap_path_subst_slash_to_dot(gchar *str)
1364 {       
1365         subst_char(str, '/', '.');
1366 }