make return receipts work again
[claws.git] / src / news.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2002 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 "defs.h"
25
26 #include <glib.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <stdlib.h>
30 #include <dirent.h>
31 #include <unistd.h>
32 #include <time.h>
33
34 #include "intl.h"
35 #include "news.h"
36 #include "nntp.h"
37 #include "socket.h"
38 #include "recv.h"
39 #include "procmsg.h"
40 #include "procheader.h"
41 #include "folder.h"
42 #include "session.h"
43 #include "statusbar.h"
44 #include "codeconv.h"
45 #include "utils.h"
46 #include "prefs_common.h"
47 #include "prefs_account.h"
48 #include "inputdialog.h"
49 #include "alertpanel.h"
50 #if USE_SSL
51 #  include "ssl.h"
52 #endif
53
54 #define NNTP_PORT       119
55 #if USE_SSL
56 #define NNTPS_PORT      563
57 #endif
58
59 static void news_folder_init             (Folder        *folder,
60                                           const gchar   *name,
61                                           const gchar   *path);
62
63 #if USE_SSL
64 static Session *news_session_new         (const gchar   *server,
65                                           gushort        port,
66                                           const gchar   *userid,
67                                           const gchar   *passwd,
68                                           SSLType        ssl_type);
69 #else
70 static Session *news_session_new         (const gchar   *server,
71                                           gushort        port,
72                                           const gchar   *userid,
73                                           const gchar   *passwd);
74 #endif
75
76 static gint news_get_article_cmd         (NNTPSession   *session,
77                                           const gchar   *cmd,
78                                           gint           num,
79                                           gchar         *filename);
80 static gint news_get_article             (NNTPSession   *session,
81                                           gint           num,
82                                           gchar         *filename);
83
84 static gint news_select_group            (NNTPSession   *session,
85                                           const gchar   *group,
86                                           gint          *num,
87                                           gint          *first,
88                                           gint          *last);
89 static GSList *news_get_uncached_articles(NNTPSession   *session,
90                                           FolderItem    *item,
91                                           gint           cache_last,
92                                           gint          *rfirst,
93                                           gint          *rlast);
94 static MsgInfo *news_parse_xover         (const gchar   *xover_str);
95 static gchar *news_parse_xhdr            (const gchar   *xhdr_str,
96                                           MsgInfo       *msginfo);
97 static GSList *news_delete_old_articles  (GSList        *alist,
98                                           FolderItem    *item,
99                                           gint           first);
100 static void news_delete_all_articles     (FolderItem    *item);
101
102 static gint news_remove_msg              (Folder        *folder, 
103                                           FolderItem    *item, 
104                                           gint           num);
105 GSList *news_get_num_list                (Folder        *folder, 
106                                           FolderItem    *item);
107 MsgInfo *news_fetch_msginfo              (Folder        *folder, 
108                                           FolderItem    *item,
109                                           gint           num);
110 GSList *news_fetch_msginfos              (Folder        *folder,
111                                           FolderItem    *item,
112                                           GSList        *msgnum_list);
113
114 Folder *news_folder_new(const gchar *name, const gchar *path)
115 {
116         Folder *folder;
117
118         folder = (Folder *)g_new0(NewsFolder, 1);
119         news_folder_init(folder, name, path);
120
121         return folder;
122 }
123
124 void news_folder_destroy(NewsFolder *folder)
125 {
126         folder_remote_folder_destroy(REMOTE_FOLDER(folder));
127 }
128
129 static void news_folder_init(Folder *folder, const gchar *name,
130                              const gchar *path)
131 {
132         folder_remote_folder_init(folder, name, path);
133
134         folder->type = F_NEWS;
135
136 /*
137         folder->get_msg_list = news_get_article_list;
138 */
139         folder->fetch_msg    = news_fetch_msg;
140 /*
141         folder->scan         = news_scan_group;
142 */
143         folder->remove_msg   = news_remove_msg;
144         folder->get_num_list = news_get_num_list;
145         folder->fetch_msginfo = news_fetch_msginfo;
146         folder->fetch_msginfos = news_fetch_msginfos;
147 }
148
149 #if USE_SSL
150 static Session *news_session_new(const gchar *server, gushort port,
151                                  const gchar *userid, const gchar *passwd,
152                                  SSLType ssl_type)
153 #else
154 static Session *news_session_new(const gchar *server, gushort port,
155                                  const gchar *userid, const gchar *passwd)
156 #endif
157 {
158         gchar buf[NNTPBUFSIZE];
159         NNTPSession *session;
160         NNTPSockInfo *nntp_sock;
161
162         g_return_val_if_fail(server != NULL, NULL);
163
164         log_message(_("creating NNTP connection to %s:%d ...\n"), server, port);
165
166 #if USE_SSL
167         if (userid && passwd)
168                 nntp_sock = nntp_open_auth(server, port, buf, userid, passwd,
169                                            ssl_type);
170         else
171                 nntp_sock = nntp_open(server, port, buf, ssl_type);
172 #else
173         if (userid && passwd)
174                 nntp_sock = nntp_open_auth(server, port, buf, userid, passwd);
175         else
176                 nntp_sock = nntp_open(server, port, buf);
177 #endif
178
179         if (nntp_sock == NULL)
180                 return NULL;
181
182         session = g_new(NNTPSession, 1);
183         SESSION(session)->type             = SESSION_NEWS;
184         SESSION(session)->server           = g_strdup(server);
185         session->nntp_sock                 = nntp_sock;
186         SESSION(session)->sock             = nntp_sock->sock;
187         SESSION(session)->connected        = TRUE;
188         SESSION(session)->phase            = SESSION_READY;
189         SESSION(session)->last_access_time = time(NULL);
190         SESSION(session)->data             = NULL;
191         session->group = NULL;
192
193         return SESSION(session);
194 }
195
196 void news_session_destroy(NNTPSession *session)
197 {
198         nntp_close(session->nntp_sock);
199         session->nntp_sock = NULL;
200         SESSION(session)->sock = NULL;
201
202         g_free(session->group);
203 }
204
205 static Session *news_session_new_for_folder(Folder *folder)
206 {
207         Session *session;
208         PrefsAccount *ac;
209         const gchar *userid = NULL;
210         gchar *passwd = NULL;
211         gushort port;
212
213         g_return_val_if_fail(folder != NULL, NULL);
214         g_return_val_if_fail(folder->account != NULL, NULL);
215
216         ac = folder->account;
217         if (ac->use_nntp_auth && ac->userid && ac->userid[0]) {
218                 userid = ac->userid;
219                 if (ac->passwd && ac->passwd[0])
220                         passwd = g_strdup(ac->passwd);
221                 else
222                         passwd = input_dialog_query_password(ac->nntp_server,
223                                                              userid);
224         }
225
226 #if USE_SSL
227         port = ac->set_nntpport ? ac->nntpport
228                 : ac->ssl_nntp ? NNTPS_PORT : NNTP_PORT;
229         session = news_session_new(ac->nntp_server, port, userid, passwd,
230                                    ac->ssl_nntp);
231 #else
232         port = ac->set_nntpport ? ac->nntpport : NNTP_PORT;
233         session = news_session_new(ac->nntp_server, port, userid, passwd);
234 #endif
235
236         g_free(passwd);
237
238         return session;
239 }
240
241 NNTPSession *news_session_get(Folder *folder)
242 {
243         RemoteFolder *rfolder = REMOTE_FOLDER(folder);
244
245         g_return_val_if_fail(folder != NULL, NULL);
246         g_return_val_if_fail(folder->type == F_NEWS, NULL);
247         g_return_val_if_fail(folder->account != NULL, NULL);
248
249         if (!rfolder->session) {
250                 rfolder->session = news_session_new_for_folder(folder);
251                 statusbar_pop_all();
252                 return NNTP_SESSION(rfolder->session);
253         }
254
255         if (time(NULL) - rfolder->session->last_access_time < SESSION_TIMEOUT) {
256                 rfolder->session->last_access_time = time(NULL);
257                 statusbar_pop_all();
258                 return NNTP_SESSION(rfolder->session);
259         }
260
261         if (nntp_mode(NNTP_SESSION(rfolder->session)->nntp_sock, FALSE)
262             != NN_SUCCESS) {
263                 log_warning(_("NNTP connection to %s:%d has been"
264                               " disconnected. Reconnecting...\n"),
265                             folder->account->nntp_server,
266                             folder->account->set_nntpport ?
267                             folder->account->nntpport : NNTP_PORT);
268                 session_destroy(rfolder->session);
269                 rfolder->session = news_session_new_for_folder(folder);
270         }
271
272         if (rfolder->session)
273                 rfolder->session->last_access_time = time(NULL);
274         statusbar_pop_all();
275         return NNTP_SESSION(rfolder->session);
276 }
277
278 GSList *news_get_article_list(Folder *folder, FolderItem *item,
279                               gboolean use_cache)
280 {
281         GSList *alist;
282         NNTPSession *session;
283
284         g_return_val_if_fail(folder != NULL, NULL);
285         g_return_val_if_fail(item != NULL, NULL);
286         g_return_val_if_fail(folder->type == F_NEWS, NULL);
287
288         session = news_session_get(folder);
289
290         if (!session) {
291                 alist = procmsg_read_cache(item, FALSE);
292                 item->last_num = procmsg_get_last_num_in_cache(alist);
293         } else if (use_cache) {
294                 GSList *newlist;
295                 gint cache_last;
296                 gint first, last;
297
298                 alist = procmsg_read_cache(item, FALSE);
299
300                 cache_last = procmsg_get_last_num_in_cache(alist);
301                 newlist = news_get_uncached_articles
302                         (session, item, cache_last, &first, &last);
303                 alist = news_delete_old_articles(alist, item, first);
304
305                 alist = g_slist_concat(alist, newlist);
306                 item->last_num = last;
307         } else {
308                 gint last;
309
310                 alist = news_get_uncached_articles
311                         (session, item, 0, NULL, &last);
312                 news_delete_all_articles(item);
313                 item->last_num = last;
314         }
315
316         procmsg_set_flags(alist, item);
317
318         statusbar_pop_all();
319
320         return alist;
321 }
322
323 gchar *news_fetch_msg(Folder *folder, FolderItem *item, gint num)
324 {
325         gchar *path, *filename;
326         NNTPSession *session;
327         gint ok;
328
329         g_return_val_if_fail(folder != NULL, NULL);
330         g_return_val_if_fail(item != NULL, NULL);
331
332         path = folder_item_get_path(item);
333         if (!is_dir_exist(path))
334                 make_dir_hier(path);
335         filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(num), NULL);
336         g_free(path);
337
338         if (is_file_exist(filename)) {
339                 debug_print(_("article %d has been already cached.\n"), num);
340                 return filename;
341         }
342
343         session = news_session_get(folder);
344         if (!session) {
345                 g_free(filename);
346                 return NULL;
347         }
348
349         ok = news_select_group(session, item->path, NULL, NULL, NULL);
350         statusbar_pop_all();
351         if (ok != NN_SUCCESS) {
352                 g_warning(_("can't select group %s\n"), item->path);
353                 g_free(filename);
354                 return NULL;
355         }
356
357         debug_print(_("getting article %d...\n"), num);
358         ok = news_get_article(NNTP_SESSION(REMOTE_FOLDER(folder)->session),
359                               num, filename);
360         statusbar_pop_all();
361         if (ok < 0) {
362                 g_warning(_("can't read article %d\n"), num);
363                 g_free(filename);
364                 return NULL;
365         }
366
367         return filename;
368 }
369
370 gint news_scan_group(Folder *folder, FolderItem *item)
371 {
372         NNTPSession *session;
373         gint num = 0, first = 0, last = 0;
374         gint ok;
375
376         g_return_val_if_fail(folder != NULL, -1);
377         g_return_val_if_fail(item != NULL, -1);
378
379         session = news_session_get(folder);
380         if (!session) return -1;
381
382         ok = news_select_group(session, item->path, &num, &first, &last);
383         if (ok != NN_SUCCESS) {
384                 log_warning(_("can't set group: %s\n"), item->path);
385                 return -1;
386         }
387
388         if (num == 0) {
389                 item->new = item->unread = item->total = item->last_num = 0;
390                 return 0;
391         }
392
393 /*
394         path = folder_item_get_path(item);
395         if (path && is_dir_exist(path)) {
396                 procmsg_get_mark_sum(path, &new, &unread, &total, &min, &max,
397                                      first);
398         }
399         g_free(path);
400
401         if (max < first || last < min)
402                 new = unread = total = num;
403         else {
404                 if (min < first)
405                         min = first;
406                 else if (first < min) {
407                         num -= min - first;
408                         first = min;
409                 }
410
411                 if (last < max)
412                         max = last;
413                 else if (max < last) {
414                         new += last - max;
415                         unread += last - max;
416                 }
417
418                 if (new > num) new = num;
419                 if (unread > num) unread = num;
420         }
421
422         item->new = new;
423         item->unread = unread;
424         item->total = num;
425         item->last_num = last;
426 */
427         return 0;
428 }
429
430 static NewsGroupInfo *news_group_info_new(const gchar *name,
431                                           gint first, gint last, gchar type)
432 {
433         NewsGroupInfo *ginfo;
434
435         ginfo = g_new(NewsGroupInfo, 1);
436         ginfo->name = g_strdup(name);
437         ginfo->first = first;
438         ginfo->last = last;
439         ginfo->type = type;
440
441         return ginfo;
442 }
443
444 static void news_group_info_free(NewsGroupInfo *ginfo)
445 {
446         g_free(ginfo->name);
447         g_free(ginfo);
448 }
449
450 static gint news_group_info_compare(NewsGroupInfo *ginfo1,
451                                     NewsGroupInfo *ginfo2)
452 {
453         return g_strcasecmp(ginfo1->name, ginfo2->name);
454 }
455
456 GSList *news_get_group_list(Folder *folder)
457 {
458         gchar *path, *filename;
459         FILE *fp;
460         GSList *list = NULL;
461         GSList *last = NULL;
462         gchar buf[NNTPBUFSIZE];
463
464         g_return_val_if_fail(folder != NULL, NULL);
465         g_return_val_if_fail(folder->type == F_NEWS, NULL);
466
467         path = folder_item_get_path(FOLDER_ITEM(folder->node->data));
468         if (!is_dir_exist(path))
469                 make_dir_hier(path);
470         filename = g_strconcat(path, G_DIR_SEPARATOR_S, NEWSGROUP_LIST, NULL);
471         g_free(path);
472
473         if ((fp = fopen(filename, "rb")) == NULL) {
474                 NNTPSession *session;
475
476                 session = news_session_get(folder);
477                 if (!session) {
478                         g_free(filename);
479                         return NULL;
480                 }
481
482                 if (nntp_list(session->nntp_sock) != NN_SUCCESS) {
483                         g_free(filename);
484                         statusbar_pop_all();
485                         return NULL;
486                 }
487                 statusbar_pop_all();
488                 if (recv_write_to_file(SESSION(session)->sock, filename) < 0) {
489                         log_warning(_("can't retrieve newsgroup list\n"));
490                         session_destroy(SESSION(session));
491                         REMOTE_FOLDER(folder)->session = NULL;
492                         g_free(filename);
493                         return NULL;
494                 }
495
496                 if ((fp = fopen(filename, "rb")) == NULL) {
497                         FILE_OP_ERROR(filename, "fopen");
498                         g_free(filename);
499                         return NULL;
500                 }
501         }
502
503         while (fgets(buf, sizeof(buf), fp) != NULL) {
504                 gchar *p = buf;
505                 gchar *name;
506                 gint last_num;
507                 gint first_num;
508                 gchar type;
509                 NewsGroupInfo *ginfo;
510
511                 p = strchr(p, ' ');
512                 if (!p) continue;
513                 *p = '\0';
514                 p++;
515                 name = buf;
516
517                 if (sscanf(p, "%d %d %c", &last_num, &first_num, &type) < 3)
518                         continue;
519
520                 ginfo = news_group_info_new(name, first_num, last_num, type);
521
522                 if (!last)
523                         last = list = g_slist_append(NULL, ginfo);
524                 else {
525                         last = g_slist_append(last, ginfo);
526                         last = last->next;
527                 }
528         }
529
530         fclose(fp);
531         g_free(filename);
532
533         list = g_slist_sort(list, (GCompareFunc)news_group_info_compare);
534
535         statusbar_pop_all();
536
537         return list;
538 }
539
540 void news_group_list_free(GSList *group_list)
541 {
542         GSList *cur;
543
544         if (!group_list) return;
545
546         for (cur = group_list; cur != NULL; cur = cur->next)
547                 news_group_info_free((NewsGroupInfo *)cur->data);
548         g_slist_free(group_list);
549 }
550
551 void news_remove_group_list_cache(Folder *folder)
552 {
553         gchar *path, *filename;
554
555         g_return_if_fail(folder != NULL);
556         g_return_if_fail(folder->type == F_NEWS);
557
558         path = folder_item_get_path(FOLDER_ITEM(folder->node->data));
559         filename = g_strconcat(path, G_DIR_SEPARATOR_S, NEWSGROUP_LIST, NULL);
560         g_free(path);
561
562         if (is_file_exist(filename)) {
563                 if (remove(filename) < 0)
564                         FILE_OP_ERROR(filename, "remove");
565         }
566         g_free(filename);
567 }
568
569 gint news_post(Folder *folder, const gchar *file)
570 {
571         NNTPSession *session;
572         FILE *fp;
573         gint ok;
574
575         g_return_val_if_fail(folder != NULL, -1);
576         g_return_val_if_fail(folder->type == F_NEWS, -1);
577         g_return_val_if_fail(file != NULL, -1);
578
579         session = news_session_get(folder);
580         if (!session) return -1;
581
582         if ((fp = fopen(file, "rb")) == NULL) {
583                 FILE_OP_ERROR(file, "fopen");
584                 return -1;
585         }
586
587         ok = nntp_post(session->nntp_sock, fp);
588         if (ok != NN_SUCCESS) {
589                 log_warning(_("can't post article.\n"));
590                 return -1;
591         }
592
593         fclose(fp);
594
595         statusbar_pop_all();
596
597         return 0;
598 }
599
600 static gint news_get_article_cmd(NNTPSession *session, const gchar *cmd,
601                                  gint num, gchar *filename)
602 {
603         gchar *msgid;
604
605         if (nntp_get_article(session->nntp_sock, cmd, num, &msgid)
606             != NN_SUCCESS)
607                 return -1;
608
609         debug_print("Message-Id = %s, num = %d\n", msgid, num);
610         g_free(msgid);
611
612         if (recv_write_to_file(session->nntp_sock->sock, filename) < 0) {
613                 log_warning(_("can't retrieve article %d\n"), num);
614                 return -1;
615         }
616
617         return 0;
618 }
619
620 static gint news_remove_msg(Folder *folder, FolderItem *item, gint num)
621 {
622         MsgInfo * msginfo;
623         gchar * filename;
624         MsgFlags msgflags = { 0, 0 };
625         gint r;
626
627         filename = folder_item_fetch_msg(item, num);
628         if (filename == NULL)
629                 return -1;
630
631         msginfo = procheader_parse_file(filename, msgflags, FALSE, FALSE);
632         if (msginfo == NULL)
633                 return -1;
634
635         r = news_cancel_article(folder, msginfo);
636
637         procmsg_msginfo_free(msginfo);
638
639         return r;
640 }
641
642 static gint news_get_article(NNTPSession *session, gint num, gchar *filename)
643 {
644         return news_get_article_cmd(session, "ARTICLE", num, filename);
645 }
646
647 /**
648  * news_select_group:
649  * @session: Active NNTP session.
650  * @group: Newsgroup name.
651  * @num: Estimated number of articles.
652  * @first: First article number.
653  * @last: Last article number.
654  *
655  * Select newsgroup @group with the GROUP command if it is not already
656  * selected in @session, or article numbers need to be returned.
657  *
658  * Return value: NNTP result code.
659  **/
660 static gint news_select_group(NNTPSession *session, const gchar *group,
661                               gint *num, gint *first, gint *last)
662 {
663         gint ok;
664         gint num_, first_, last_;
665
666         if (!num || !first || !last) {
667                 if (session->group && g_strcasecmp(session->group, group) == 0)
668                         return NN_SUCCESS;
669                 num = &num_;
670                 first = &first_;
671                 last = &last_;
672         }
673
674         g_free(session->group);
675         session->group = NULL;
676
677         ok = nntp_group(session->nntp_sock, group, num, first, last);
678         if (ok == NN_SUCCESS)
679                 session->group = g_strdup(group);
680
681         return ok;
682 }
683
684 static GSList *news_get_uncached_articles(NNTPSession *session,
685                                           FolderItem *item, gint cache_last,
686                                           gint *rfirst, gint *rlast)
687 {
688         gint ok;
689         gint num = 0, first = 0, last = 0, begin = 0, end = 0;
690         gchar buf[NNTPBUFSIZE];
691         GSList *newlist = NULL;
692         GSList *llast = NULL;
693         MsgInfo *msginfo;
694
695         if (rfirst) *rfirst = 0;
696         if (rlast)  *rlast  = 0;
697
698         g_return_val_if_fail(session != NULL, NULL);
699         g_return_val_if_fail(item != NULL, NULL);
700         g_return_val_if_fail(item->folder != NULL, NULL);
701         g_return_val_if_fail(item->folder->type == F_NEWS, NULL);
702
703         ok = news_select_group(session, item->path, &num, &first, &last);
704         if (ok != NN_SUCCESS) {
705                 log_warning(_("can't set group: %s\n"), item->path);
706                 return NULL;
707         }
708
709         /* calculate getting overview range */
710         if (first > last) {
711                 log_warning(_("invalid article range: %d - %d\n"),
712                             first, last);
713                 return NULL;
714         }
715         if (cache_last < first)
716                 begin = first;
717         else if (last < cache_last)
718                 begin = first;
719         else if (last == cache_last) {
720                 debug_print(_("no new articles.\n"));
721                 return NULL;
722         } else
723                 begin = cache_last + 1;
724         end = last;
725
726         if (rfirst) *rfirst = first;
727         if (rlast)  *rlast  = last;
728
729         if (prefs_common.max_articles > 0 &&
730             end - begin + 1 > prefs_common.max_articles)
731                 begin = end - prefs_common.max_articles + 1;
732
733         log_message(_("getting xover %d - %d in %s...\n"),
734                     begin, end, item->path);
735         if (nntp_xover(session->nntp_sock, begin, end) != NN_SUCCESS) {
736                 log_warning(_("can't get xover\n"));
737                 return NULL;
738         }
739
740         for (;;) {
741                 if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
742                         log_warning(_("error occurred while getting xover.\n"));
743                         return newlist;
744                 }
745
746                 if (buf[0] == '.' && buf[1] == '\r') break;
747
748                 msginfo = news_parse_xover(buf);
749                 if (!msginfo) {
750                         log_warning(_("invalid xover line: %s\n"), buf);
751                         continue;
752                 }
753
754                 msginfo->folder = item;
755                 msginfo->flags.perm_flags = MSG_NEW|MSG_UNREAD;
756                 msginfo->flags.tmp_flags = MSG_NEWS;
757                 msginfo->newsgroups = g_strdup(item->path);
758
759                 if (!newlist)
760                         llast = newlist = g_slist_append(newlist, msginfo);
761                 else {
762                         llast = g_slist_append(llast, msginfo);
763                         llast = llast->next;
764                 }
765         }
766
767         if (nntp_xhdr(session->nntp_sock, "to", begin, end) != NN_SUCCESS) {
768                 log_warning(_("can't get xhdr\n"));
769                 return newlist;
770         }
771
772         llast = newlist;
773
774         for (;;) {
775                 if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
776                         log_warning(_("error occurred while getting xhdr.\n"));
777                         return newlist;
778                 }
779
780                 if (buf[0] == '.' && buf[1] == '\r') break;
781                 if (!llast) {
782                         g_warning("llast == NULL\n");
783                         continue;
784                 }
785
786                 msginfo = (MsgInfo *)llast->data;
787                 msginfo->to = news_parse_xhdr(buf, msginfo);
788
789                 llast = llast->next;
790         }
791
792         if (nntp_xhdr(session->nntp_sock, "cc", begin, end) != NN_SUCCESS) {
793                 log_warning(_("can't get xhdr\n"));
794                 return newlist;
795         }
796
797         llast = newlist;
798
799         for (;;) {
800                 if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
801                         log_warning(_("error occurred while getting xhdr.\n"));
802                         return newlist;
803                 }
804
805                 if (buf[0] == '.' && buf[1] == '\r') break;
806                 if (!llast) {
807                         g_warning("llast == NULL\n");
808                         continue;
809                 }
810
811                 msginfo = (MsgInfo *)llast->data;
812                 msginfo->cc = news_parse_xhdr(buf, msginfo);
813
814                 llast = llast->next;
815         }
816
817         return newlist;
818 }
819
820 #define PARSE_ONE_PARAM(p, srcp) \
821 { \
822         p = strchr(srcp, '\t'); \
823         if (!p) return NULL; \
824         else \
825                 *p++ = '\0'; \
826 }
827
828 static MsgInfo *news_parse_xover(const gchar *xover_str)
829 {
830         MsgInfo *msginfo;
831         gchar buf[NNTPBUFSIZE];
832         gchar *subject, *sender, *size, *line, *date, *msgid, *ref, *tmp, *xref;
833         gchar *p;
834         gint num, size_int, line_int;
835         gchar *xover_buf;
836
837         Xstrdup_a(xover_buf, xover_str, return NULL);
838
839         PARSE_ONE_PARAM(subject, xover_buf);
840         PARSE_ONE_PARAM(sender, subject);
841         PARSE_ONE_PARAM(date, sender);
842         PARSE_ONE_PARAM(msgid, date);
843         PARSE_ONE_PARAM(ref, msgid);
844         PARSE_ONE_PARAM(size, ref);
845         PARSE_ONE_PARAM(line, size);
846         PARSE_ONE_PARAM(xref, line);
847
848         tmp = strchr(xref, '\t');
849         if (!tmp) tmp = strchr(line, '\r');
850         if (!tmp) tmp = strchr(line, '\n');
851         if (tmp) *tmp = '\0';
852
853         num = atoi(xover_str);
854         size_int = atoi(size);
855         line_int = atoi(line);
856
857         /* set MsgInfo */
858         msginfo = procmsg_msginfo_new();
859         msginfo->msgnum = num;
860         msginfo->size = size_int;
861
862         msginfo->date = g_strdup(date);
863         msginfo->date_t = procheader_date_parse(NULL, date, 0);
864
865         conv_unmime_header(buf, sizeof(buf), sender, NULL);
866         msginfo->from = g_strdup(buf);
867         msginfo->fromname = procheader_get_fromname(buf);
868
869         conv_unmime_header(buf, sizeof(buf), subject, NULL);
870         msginfo->subject = g_strdup(buf);
871
872         extract_parenthesis(msgid, '<', '>');
873         remove_space(msgid);
874         if (*msgid != '\0')
875                 msginfo->msgid = g_strdup(msgid);
876
877         msginfo->references = g_strdup(ref);
878         eliminate_parenthesis(ref, '(', ')');
879         if ((p = strrchr(ref, '<')) != NULL) {
880                 extract_parenthesis(p, '<', '>');
881                 remove_space(p);
882                 if (*p != '\0')
883                         msginfo->inreplyto = g_strdup(p);
884         }
885
886         msginfo->xref = g_strdup(xref);
887         p = msginfo->xref+strlen(msginfo->xref) - 1;
888         while (*p == '\r' || *p == '\n') {
889                 *p = '\0';
890                 p--;
891         }
892
893         return msginfo;
894 }
895
896 static gchar *news_parse_xhdr(const gchar *xhdr_str, MsgInfo *msginfo)
897 {
898         gchar *p;
899         gchar *tmp;
900         gint num;
901
902         p = strchr(xhdr_str, ' ');
903         if (!p)
904                 return NULL;
905         else
906                 p++;
907
908         num = atoi(xhdr_str);
909         if (msginfo->msgnum != num) return NULL;
910
911         tmp = strchr(p, '\r');
912         if (!tmp) tmp = strchr(p, '\n');
913
914         if (tmp)
915                 return g_strndup(p, tmp - p);
916         else
917                 return g_strdup(p);
918 }
919
920 static GSList *news_delete_old_articles(GSList *alist, FolderItem *item,
921                                         gint first)
922 {
923         GSList *cur, *next;
924         MsgInfo *msginfo;
925         gchar *dir;
926
927         g_return_val_if_fail(item != NULL, alist);
928         g_return_val_if_fail(item->folder != NULL, alist);
929         g_return_val_if_fail(item->folder->type == F_NEWS, alist);
930
931         if (first < 2) return alist;
932
933         debug_print(_("Deleting cached articles 1 - %d ... "), first - 1);
934
935         dir = folder_item_get_path(item);
936         remove_numbered_files(dir, 1, first - 1);
937         g_free(dir);
938
939         for (cur = alist; cur != NULL; ) {
940                 next = cur->next;
941
942                 msginfo = (MsgInfo *)cur->data;
943                 if (msginfo && msginfo->msgnum < first) {
944                         procmsg_msginfo_free(msginfo);
945                         alist = g_slist_remove(alist, msginfo);
946                 }
947
948                 cur = next;
949         }
950         debug_print(_("done.\n"));
951
952         return alist;
953 }
954
955 static void news_delete_all_articles(FolderItem *item)
956 {
957         gchar *dir;
958
959         g_return_if_fail(item != NULL);
960         g_return_if_fail(item->folder != NULL);
961         g_return_if_fail(item->folder->type == F_NEWS);
962
963         debug_print(_("\tDeleting all cached articles... "));
964
965         dir = folder_item_get_path(item);
966         remove_all_numbered_files(dir);
967         g_free(dir);
968
969         debug_print(_("done.\n"));
970 }
971
972 gint news_cancel_article(Folder * folder, MsgInfo * msginfo)
973 {
974         gchar * tmp;
975         FILE * tmpfp;
976         gchar buf[BUFFSIZE];
977
978         tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
979                               G_DIR_SEPARATOR, (gint)msginfo);
980         if (tmp == NULL)
981                 return -1;
982
983         if ((tmpfp = fopen(tmp, "wb")) == NULL) {
984                 FILE_OP_ERROR(tmp, "fopen");
985                 return -1;
986         }
987         if (change_file_mode_rw(tmpfp, tmp) < 0) {
988                 FILE_OP_ERROR(tmp, "chmod");
989                 g_warning(_("can't change file mode\n"));
990         }
991         
992         fprintf(tmpfp, "From: %s\r\n", msginfo->from);
993         fprintf(tmpfp, "Newsgroups: %s\r\n", msginfo->newsgroups);
994         fprintf(tmpfp, "Subject: cmsg cancel <%s>\r\n", msginfo->msgid);
995         fprintf(tmpfp, "Control: cancel <%s>\r\n", msginfo->msgid);
996         fprintf(tmpfp, "Approved: %s\r\n", msginfo->from);
997         fprintf(tmpfp, "X-Cancelled-by: %s\r\n", msginfo->from);
998         get_rfc822_date(buf, sizeof(buf));
999         fprintf(tmpfp, "Date: %s\r\n", buf);
1000         fprintf(tmpfp, "\r\n");
1001         fprintf(tmpfp, "removed with sylpheed\r\n");
1002
1003         fclose(tmpfp);
1004
1005         news_post(folder, tmp);
1006         remove(tmp);
1007
1008         g_free(tmp);
1009
1010         return 0;
1011 }
1012
1013 GSList *news_get_num_list(Folder *folder, FolderItem *item)
1014 {
1015         NNTPSession *session;
1016         gint i, ok, num, first, last;
1017         GSList *msgnum_list = NULL;
1018
1019         session = news_session_get(folder);
1020         g_return_val_if_fail(session != NULL, NULL);
1021         g_return_val_if_fail(item != NULL, NULL);
1022         g_return_val_if_fail(item->folder != NULL, NULL);
1023         g_return_val_if_fail(item->folder->type == F_NEWS, NULL);
1024
1025         ok = news_select_group(session, item->path, &num, &first, &last);
1026         if (ok != NN_SUCCESS) {
1027                 log_warning(_("can't set group: %s\n"), item->path);
1028                 return NULL;
1029         }
1030
1031         if(last < first) {
1032                 log_warning(_("invalid article range: %d - %d\n"),
1033                             first, last);
1034                 return NULL;
1035         }
1036
1037         for(i = first; i <= last; i++) {
1038                 msgnum_list = g_slist_prepend(msgnum_list, GINT_TO_POINTER(i));
1039         }
1040
1041         return msgnum_list;
1042 }
1043
1044 #define READ_TO_LISTEND(hdr) \
1045         while (!(buf[0] == '.' && buf[1] == '\r')) { \
1046                 if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) { \
1047                         log_warning(_("error occurred while getting %s.\n"), hdr); \
1048                         return msginfo; \
1049                 } \
1050         }
1051
1052 MsgInfo *news_fetch_msginfo(Folder *folder, FolderItem *item, gint num)
1053 {
1054         NNTPSession *session;
1055         MsgInfo *msginfo = NULL;
1056         gchar buf[NNTPBUFSIZE];
1057
1058         session = news_session_get(folder);
1059         g_return_val_if_fail(session != NULL, NULL);
1060         g_return_val_if_fail(item != NULL, NULL);
1061         g_return_val_if_fail(item->folder != NULL, NULL);
1062         g_return_val_if_fail(item->folder->type == F_NEWS, NULL);
1063
1064         log_message(_("getting xover %d in %s...\n"),
1065                     num, item->path);
1066         if (nntp_xover(session->nntp_sock, num, num) != NN_SUCCESS) {
1067                 log_warning(_("can't get xover\n"));
1068                 return NULL;
1069         }
1070         
1071         if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
1072                 log_warning(_("error occurred while getting xover.\n"));
1073                 return NULL;
1074         }
1075         
1076         msginfo = news_parse_xover(buf);
1077         if (!msginfo) {
1078                 log_warning(_("invalid xover line: %s\n"), buf);
1079         }
1080
1081         READ_TO_LISTEND("xover");
1082
1083         if(!msginfo)
1084                 return NULL;
1085         
1086         msginfo->folder = item;
1087         msginfo->flags.perm_flags = MSG_NEW|MSG_UNREAD;
1088         msginfo->flags.tmp_flags = MSG_NEWS;
1089         msginfo->newsgroups = g_strdup(item->path);
1090
1091         if (nntp_xhdr(session->nntp_sock, "to", num, num) != NN_SUCCESS) {
1092                 log_warning(_("can't get xhdr\n"));
1093                 return msginfo;
1094         }
1095
1096         if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
1097                 log_warning(_("error occurred while getting xhdr.\n"));
1098                 return msginfo;
1099         }
1100
1101         msginfo->to = news_parse_xhdr(buf, msginfo);
1102
1103         READ_TO_LISTEND("xhdr (to)");
1104
1105         if (nntp_xhdr(session->nntp_sock, "cc", num, num) != NN_SUCCESS) {
1106                 log_warning(_("can't get xhdr\n"));
1107                 return msginfo;
1108         }
1109
1110         if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
1111                 log_warning(_("error occurred while getting xhdr.\n"));
1112                 return msginfo;
1113         }
1114
1115         msginfo->cc = news_parse_xhdr(buf, msginfo);
1116
1117         READ_TO_LISTEND("xhdr (cc)");
1118
1119         return msginfo;
1120 }
1121
1122 static GSList *news_fetch_msginfo_from_to(NNTPSession *session, FolderItem *item, guint begin, guint end)
1123 {
1124         gchar buf[NNTPBUFSIZE];
1125         GSList *newlist = NULL;
1126         GSList *llast = NULL;
1127         MsgInfo *msginfo;
1128
1129         g_return_val_if_fail(session != NULL, NULL);
1130         g_return_val_if_fail(item != NULL, NULL);
1131
1132         log_message(_("getting xover %d - %d in %s...\n"),
1133                     begin, end, item->path);
1134         if (nntp_xover(session->nntp_sock, begin, end) != NN_SUCCESS) {
1135                 log_warning(_("can't get xover\n"));
1136                 return NULL;
1137         }
1138
1139         for (;;) {
1140                 if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
1141                         log_warning(_("error occurred while getting xover.\n"));
1142                         return newlist;
1143                 }
1144
1145                 if (buf[0] == '.' && buf[1] == '\r') break;
1146
1147                 msginfo = news_parse_xover(buf);
1148                 if (!msginfo) {
1149                         log_warning(_("invalid xover line: %s\n"), buf);
1150                         continue;
1151                 }
1152
1153                 msginfo->folder = item;
1154                 msginfo->flags.perm_flags = MSG_NEW|MSG_UNREAD;
1155                 msginfo->flags.tmp_flags = MSG_NEWS;
1156                 msginfo->newsgroups = g_strdup(item->path);
1157
1158                 if (!newlist)
1159                         llast = newlist = g_slist_append(newlist, msginfo);
1160                 else {
1161                         llast = g_slist_append(llast, msginfo);
1162                         llast = llast->next;
1163                 }
1164         }
1165
1166         if (nntp_xhdr(session->nntp_sock, "to", begin, end) != NN_SUCCESS) {
1167                 log_warning(_("can't get xhdr\n"));
1168                 return newlist;
1169         }
1170
1171         llast = newlist;
1172
1173         for (;;) {
1174                 if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
1175                         log_warning(_("error occurred while getting xhdr.\n"));
1176                         return newlist;
1177                 }
1178
1179                 if (buf[0] == '.' && buf[1] == '\r') break;
1180                 if (!llast) {
1181                         g_warning("llast == NULL\n");
1182                         continue;
1183                 }
1184
1185                 msginfo = (MsgInfo *)llast->data;
1186                 msginfo->to = news_parse_xhdr(buf, msginfo);
1187
1188                 llast = llast->next;
1189         }
1190
1191         if (nntp_xhdr(session->nntp_sock, "cc", begin, end) != NN_SUCCESS) {
1192                 log_warning(_("can't get xhdr\n"));
1193                 return newlist;
1194         }
1195
1196         llast = newlist;
1197
1198         for (;;) {
1199                 if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
1200                         log_warning(_("error occurred while getting xhdr.\n"));
1201                         return newlist;
1202                 }
1203
1204                 if (buf[0] == '.' && buf[1] == '\r') break;
1205                 if (!llast) {
1206                         g_warning("llast == NULL\n");
1207                         continue;
1208                 }
1209
1210                 msginfo = (MsgInfo *)llast->data;
1211                 msginfo->cc = news_parse_xhdr(buf, msginfo);
1212
1213                 llast = llast->next;
1214         }
1215
1216         return newlist;
1217 }
1218
1219 gint news_fetch_msgnum_sort(gconstpointer a, gconstpointer b)
1220 {
1221         return (GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b));
1222 }
1223
1224 GSList *news_fetch_msginfos(Folder *folder, FolderItem *item, GSList *msgnum_list)
1225 {
1226         NNTPSession *session;
1227         GSList *elem, *msginfo_list = NULL, *tmp_msgnum_list, *tmp_msginfo_list;
1228         guint first, last, next;
1229         
1230         g_return_val_if_fail(folder != NULL, NULL);
1231         g_return_val_if_fail(folder->type == F_NEWS, NULL);
1232         g_return_val_if_fail(msgnum_list != NULL, NULL);
1233         g_return_val_if_fail(item != NULL, NULL);
1234         
1235         session = news_session_get(folder);
1236         g_return_val_if_fail(session != NULL, NULL);
1237
1238         tmp_msgnum_list = g_slist_copy(msgnum_list);
1239         tmp_msgnum_list = g_slist_sort(tmp_msgnum_list, news_fetch_msgnum_sort);
1240
1241         first = GPOINTER_TO_INT(tmp_msgnum_list->data);
1242         last = first;
1243         for(elem = g_slist_next(tmp_msgnum_list); elem != NULL; elem = g_slist_next(elem)) {
1244                 next = GPOINTER_TO_INT(elem->data);
1245                 if(next != (last + 1)) {
1246                         tmp_msginfo_list = news_fetch_msginfo_from_to(session, item, first, last);
1247                         msginfo_list = g_slist_concat(msginfo_list, tmp_msginfo_list);
1248                         first = next;
1249                 }
1250                 last = next;
1251         }
1252         tmp_msginfo_list = news_fetch_msginfo_from_to(session, item, first, last);
1253         msginfo_list = g_slist_concat(msginfo_list, tmp_msginfo_list);
1254
1255         g_slist_free(tmp_msgnum_list);
1256         
1257         return msginfo_list;
1258 }