8cd1c0b8e2a3210c41582de4b8943b3c0c841fdf
[claws.git] / src / news.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2003 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 "log.h"
50 #include "progressindicator.h"
51 #include "remotefolder.h"
52 #include "alertpanel.h"
53 #if USE_OPENSSL
54 #  include "ssl.h"
55 #endif
56
57 #define NNTP_PORT       119
58 #if USE_OPENSSL
59 #define NNTPS_PORT      563
60 #endif
61
62 typedef struct _NewsFolder      NewsFolder;
63
64 #define NEWS_FOLDER(obj)        ((NewsFolder *)obj)
65
66 struct _NewsFolder
67 {
68         RemoteFolder rfolder;
69
70         gboolean use_auth;
71 };
72
73 static void news_folder_init             (Folder        *folder,
74                                           const gchar   *name,
75                                           const gchar   *path);
76
77 static Folder   *news_folder_new        (const gchar    *name,
78                                          const gchar    *folder);
79 static void      news_folder_destroy    (Folder         *folder);
80
81 static gchar *news_fetch_msg            (Folder         *folder,
82                                          FolderItem     *item,
83                                          gint            num);
84
85
86 #if USE_OPENSSL
87 static Session *news_session_new         (const gchar   *server,
88                                           gushort        port,
89                                           const gchar   *userid,
90                                           const gchar   *passwd,
91                                           SSLType        ssl_type);
92 #else
93 static Session *news_session_new         (const gchar   *server,
94                                           gushort        port,
95                                           const gchar   *userid,
96                                           const gchar   *passwd);
97 #endif
98
99 static gint news_get_article_cmd         (NNTPSession   *session,
100                                           const gchar   *cmd,
101                                           gint           num,
102                                           gchar         *filename);
103 static gint news_get_article             (NNTPSession   *session,
104                                           gint           num,
105                                           gchar         *filename);
106
107 static gint news_select_group            (NNTPSession   *session,
108                                           const gchar   *group,
109                                           gint          *num,
110                                           gint          *first,
111                                           gint          *last);
112 static MsgInfo *news_parse_xover         (const gchar   *xover_str);
113 static gchar *news_parse_xhdr            (const gchar   *xhdr_str,
114                                           MsgInfo       *msginfo);
115 gint news_get_num_list                   (Folder        *folder, 
116                                           FolderItem    *item,
117                                           GSList       **list,
118                                           gboolean      *old_uids_valid);
119 MsgInfo *news_get_msginfo                (Folder        *folder, 
120                                           FolderItem    *item,
121                                           gint           num);
122 GSList *news_get_msginfos                (Folder        *folder,
123                                           FolderItem    *item,
124                                           GSList        *msgnum_list);
125 gboolean news_scan_required              (Folder        *folder,
126                                           FolderItem    *item);
127
128 gint news_post_stream                    (Folder        *folder, 
129                                           FILE          *fp);
130 static gchar *news_folder_get_path       (Folder        *folder);
131 gchar *news_item_get_path                (Folder        *folder,
132                                           FolderItem    *item);
133
134 static FolderClass news_class =
135 {
136         F_NEWS,
137         "news",
138         "News",
139
140         /* Folder functions */
141         news_folder_new,
142         news_folder_destroy,
143         NULL,
144         NULL,
145         NULL,
146         NULL,
147
148         /* FolderItem functions */
149         NULL,
150         NULL,
151         NULL,
152         NULL,
153         news_item_get_path,
154         NULL,
155         NULL,
156         NULL,
157         NULL,
158         news_get_num_list,
159         NULL,
160         NULL,
161         NULL,
162         news_scan_required,
163
164         /* Message functions */
165         news_get_msginfo,
166         news_get_msginfos,
167         news_fetch_msg,
168         NULL,
169         NULL,
170         NULL,
171         NULL,
172         NULL,
173         NULL,
174         NULL,
175         NULL,
176 };
177
178 FolderClass *news_get_class(void)
179 {
180         return &news_class;
181 }
182
183 static Folder *news_folder_new(const gchar *name, const gchar *path)
184 {
185         Folder *folder;
186
187         folder = (Folder *)g_new0(NewsFolder, 1);
188         folder->klass = &news_class;
189         news_folder_init(folder, name, path);
190
191         return folder;
192 }
193
194 static void news_folder_destroy(Folder *folder)
195 {
196         gchar *dir;
197
198         dir = news_folder_get_path(folder);
199         if (is_dir_exist(dir))
200                 remove_dir_recursive(dir);
201         g_free(dir);
202
203         folder_remote_folder_destroy(REMOTE_FOLDER(folder));
204 }
205
206 static void news_folder_init(Folder *folder, const gchar *name,
207                              const gchar *path)
208 {
209         folder_remote_folder_init(folder, name, path);
210 }
211
212 #if USE_OPENSSL
213 static Session *news_session_new(const gchar *server, gushort port,
214                                  const gchar *userid, const gchar *passwd,
215                                  SSLType ssl_type)
216 #else
217 static Session *news_session_new(const gchar *server, gushort port,
218                                  const gchar *userid, const gchar *passwd)
219 #endif
220 {
221         gchar buf[NNTPBUFSIZE];
222         Session *session;
223
224         g_return_val_if_fail(server != NULL, NULL);
225
226         log_message(_("creating NNTP connection to %s:%d ...\n"), server, port);
227
228 #if USE_OPENSSL
229         session = nntp_session_new(server, port, buf, userid, passwd, ssl_type);
230 #else
231         session = nntp_session_new(server, port, buf, userid, passwd);
232 #endif
233
234         return session;
235 }
236
237 static Session *news_session_new_for_folder(Folder *folder)
238 {
239         Session *session;
240         PrefsAccount *ac;
241         const gchar *userid = NULL;
242         gchar *passwd = NULL;
243         gushort port;
244         gchar buf[NNTPBUFSIZE];
245
246         g_return_val_if_fail(folder != NULL, NULL);
247         g_return_val_if_fail(folder->account != NULL, NULL);
248
249         ac = folder->account;
250         if (ac->use_nntp_auth && ac->userid && ac->userid[0]) {
251                 userid = ac->userid;
252                 if (ac->passwd && ac->passwd[0])
253                         passwd = g_strdup(ac->passwd);
254                 else
255                         passwd = input_dialog_query_password(ac->nntp_server,
256                                                              userid);
257         }
258
259 #if USE_OPENSSL
260         port = ac->set_nntpport ? ac->nntpport
261                 : ac->ssl_nntp ? NNTPS_PORT : NNTP_PORT;
262         session = news_session_new(ac->nntp_server, port, userid, passwd,
263                                    ac->ssl_nntp);
264 #else
265         port = ac->set_nntpport ? ac->nntpport : NNTP_PORT;
266         session = news_session_new(ac->nntp_server, port, userid, passwd);
267 #endif
268         if ((session != NULL) && ac->use_nntp_auth && ac->use_nntp_auth_onconnect)
269                 nntp_forceauth(NNTP_SESSION(session), buf, userid, passwd);
270
271         g_free(passwd);
272
273         return session;
274 }
275
276 static NNTPSession *news_session_get(Folder *folder)
277 {
278         RemoteFolder *rfolder = REMOTE_FOLDER(folder);
279
280         g_return_val_if_fail(folder != NULL, NULL);
281         g_return_val_if_fail(FOLDER_CLASS(folder) == &news_class, NULL);
282         g_return_val_if_fail(folder->account != NULL, NULL);
283
284         if (prefs_common.work_offline)
285                 return NULL;
286
287         if (!rfolder->session) {
288                 rfolder->session = news_session_new_for_folder(folder);
289                 return NNTP_SESSION(rfolder->session);
290         }
291
292         if (time(NULL) - rfolder->session->last_access_time < SESSION_TIMEOUT) {
293                 rfolder->session->last_access_time = time(NULL);
294                 return NNTP_SESSION(rfolder->session);
295         }
296
297         if (nntp_mode(NNTP_SESSION(rfolder->session), FALSE)
298             != NN_SUCCESS) {
299                 log_warning("NNTP connection to %s:%d has been"
300                               " disconnected. Reconnecting...\n",
301                             folder->account->nntp_server,
302                             folder->account->set_nntpport ?
303                             folder->account->nntpport : NNTP_PORT);
304                 session_destroy(rfolder->session);
305                 rfolder->session = news_session_new_for_folder(folder);
306         }
307
308         if (rfolder->session)
309                 rfolder->session->last_access_time = time(NULL);
310         return NNTP_SESSION(rfolder->session);
311 }
312
313 static gchar *news_fetch_msg(Folder *folder, FolderItem *item, gint num)
314 {
315         gchar *path, *filename;
316         NNTPSession *session;
317         gint ok;
318
319         g_return_val_if_fail(folder != NULL, NULL);
320         g_return_val_if_fail(item != NULL, NULL);
321
322         path = folder_item_get_path(item);
323         if (!is_dir_exist(path))
324                 make_dir_hier(path);
325         filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(num), NULL);
326         g_free(path);
327
328         if (is_file_exist(filename)) {
329                 debug_print("article %d has been already cached.\n", num);
330                 return filename;
331         }
332
333         session = news_session_get(folder);
334         if (!session) {
335                 g_free(filename);
336                 return NULL;
337         }
338
339         ok = news_select_group(session, item->path, NULL, NULL, NULL);
340         if (ok != NN_SUCCESS) {
341                 g_warning("can't select group %s\n", item->path);
342                 g_free(filename);
343                 return NULL;
344         }
345
346         debug_print("getting article %d...\n", num);
347         ok = news_get_article(NNTP_SESSION(REMOTE_FOLDER(folder)->session),
348                               num, filename);
349         if (ok < 0) {
350                 g_warning("can't read article %d\n", num);
351                 session_destroy(SESSION(session));
352                 REMOTE_FOLDER(folder)->session = NULL;
353                 g_free(filename);
354                 return NULL;
355         }
356
357         return filename;
358 }
359
360 static NewsGroupInfo *news_group_info_new(const gchar *name,
361                                           gint first, gint last, gchar type)
362 {
363         NewsGroupInfo *ginfo;
364
365         ginfo = g_new(NewsGroupInfo, 1);
366         ginfo->name = g_strdup(name);
367         ginfo->first = first;
368         ginfo->last = last;
369         ginfo->type = type;
370
371         return ginfo;
372 }
373
374 static void news_group_info_free(NewsGroupInfo *ginfo)
375 {
376         g_free(ginfo->name);
377         g_free(ginfo);
378 }
379
380 static gint news_group_info_compare(NewsGroupInfo *ginfo1,
381                                     NewsGroupInfo *ginfo2)
382 {
383         return g_strcasecmp(ginfo1->name, ginfo2->name);
384 }
385
386 GSList *news_get_group_list(Folder *folder)
387 {
388         gchar *path, *filename;
389         FILE *fp;
390         GSList *list = NULL;
391         GSList *last = NULL;
392         gchar buf[NNTPBUFSIZE];
393
394         g_return_val_if_fail(folder != NULL, NULL);
395         g_return_val_if_fail(FOLDER_CLASS(folder) == &news_class, NULL);
396
397         path = folder_item_get_path(FOLDER_ITEM(folder->node->data));
398         if (!is_dir_exist(path))
399                 make_dir_hier(path);
400         filename = g_strconcat(path, G_DIR_SEPARATOR_S, NEWSGROUP_LIST, NULL);
401         g_free(path);
402
403         if ((fp = fopen(filename, "rb")) == NULL) {
404                 NNTPSession *session;
405
406                 session = news_session_get(folder);
407                 if (!session) {
408                         g_free(filename);
409                         return NULL;
410                 }
411
412                 if (nntp_list(session) != NN_SUCCESS) {
413                         g_free(filename);
414                         return NULL;
415                 }
416                 if (recv_write_to_file(SESSION(session)->sock, filename) < 0) {
417                         log_warning("can't retrieve newsgroup list\n");
418                         session_destroy(SESSION(session));
419                         REMOTE_FOLDER(folder)->session = NULL;
420                         g_free(filename);
421                         return NULL;
422                 }
423
424                 if ((fp = fopen(filename, "rb")) == NULL) {
425                         FILE_OP_ERROR(filename, "fopen");
426                         g_free(filename);
427                         return NULL;
428                 }
429         }
430
431         while (fgets(buf, sizeof(buf), fp) != NULL) {
432                 gchar *p = buf;
433                 gchar *name;
434                 gint last_num;
435                 gint first_num;
436                 gchar type;
437                 NewsGroupInfo *ginfo;
438
439                 p = strchr(p, ' ');
440                 if (!p) continue;
441                 *p = '\0';
442                 p++;
443                 name = buf;
444
445                 if (sscanf(p, "%d %d %c", &last_num, &first_num, &type) < 3)
446                         continue;
447
448                 ginfo = news_group_info_new(name, first_num, last_num, type);
449
450                 if (!last)
451                         last = list = g_slist_append(NULL, ginfo);
452                 else {
453                         last = g_slist_append(last, ginfo);
454                         last = last->next;
455                 }
456         }
457
458         fclose(fp);
459         g_free(filename);
460
461         list = g_slist_sort(list, (GCompareFunc)news_group_info_compare);
462
463         return list;
464 }
465
466 void news_group_list_free(GSList *group_list)
467 {
468         GSList *cur;
469
470         if (!group_list) return;
471
472         for (cur = group_list; cur != NULL; cur = cur->next)
473                 news_group_info_free((NewsGroupInfo *)cur->data);
474         g_slist_free(group_list);
475 }
476
477 void news_remove_group_list_cache(Folder *folder)
478 {
479         gchar *path, *filename;
480
481         g_return_if_fail(folder != NULL);
482         g_return_if_fail(FOLDER_CLASS(folder) == &news_class);
483
484         path = folder_item_get_path(FOLDER_ITEM(folder->node->data));
485         filename = g_strconcat(path, G_DIR_SEPARATOR_S, NEWSGROUP_LIST, NULL);
486         g_free(path);
487
488         if (is_file_exist(filename)) {
489                 if (remove(filename) < 0)
490                         FILE_OP_ERROR(filename, "remove");
491         }
492         g_free(filename);
493 }
494
495 gint news_post(Folder *folder, const gchar *file)
496 {
497         FILE *fp;
498         gint ok;
499
500         g_return_val_if_fail(folder != NULL, -1);
501         g_return_val_if_fail(FOLDER_CLASS(folder) == &news_class, -1);
502         g_return_val_if_fail(file != NULL, -1);
503
504         if ((fp = fopen(file, "rb")) == NULL) {
505                 FILE_OP_ERROR(file, "fopen");
506                 return -1;
507         }
508
509         ok = news_post_stream(folder, fp);
510
511         fclose(fp);
512
513         return ok;
514 }
515
516 gint news_post_stream(Folder *folder, FILE *fp)
517 {
518         NNTPSession *session;
519         gint ok;
520
521         g_return_val_if_fail(folder != NULL, -1);
522         g_return_val_if_fail(FOLDER_CLASS(folder) == &news_class, -1);
523         g_return_val_if_fail(fp != NULL, -1);
524
525         session = news_session_get(folder);
526         if (!session) return -1;
527
528         ok = nntp_post(session, fp);
529         if (ok != NN_SUCCESS) {
530                 log_warning("can't post article.\n");
531                 return -1;
532         }
533
534         return 0;
535 }
536
537 static gint news_get_article_cmd(NNTPSession *session, const gchar *cmd,
538                                  gint num, gchar *filename)
539 {
540         gchar *msgid;
541
542         if (nntp_get_article(session, cmd, num, &msgid)
543             != NN_SUCCESS)
544                 return -1;
545
546         debug_print("Message-Id = %s, num = %d\n", msgid, num);
547         g_free(msgid);
548
549         if (recv_write_to_file(SESSION(session)->sock, filename) < 0) {
550                 log_warning("can't retrieve article %d\n", num);
551                 return -1;
552         }
553
554         return 0;
555 }
556
557 static gint news_get_article(NNTPSession *session, gint num, gchar *filename)
558 {
559         return news_get_article_cmd(session, "ARTICLE", num, filename);
560 }
561
562 /**
563  * news_select_group:
564  * @session: Active NNTP session.
565  * @group: Newsgroup name.
566  * @num: Estimated number of articles.
567  * @first: First article number.
568  * @last: Last article number.
569  *
570  * Select newsgroup @group with the GROUP command if it is not already
571  * selected in @session, or article numbers need to be returned.
572  *
573  * Return value: NNTP result code.
574  **/
575 static gint news_select_group(NNTPSession *session, const gchar *group,
576                               gint *num, gint *first, gint *last)
577 {
578         gint ok;
579         gint num_, first_, last_;
580
581         if (!num || !first || !last) {
582                 if (session->group && g_strcasecmp(session->group, group) == 0)
583                         return NN_SUCCESS;
584                 num = &num_;
585                 first = &first_;
586                 last = &last_;
587         }
588
589         g_free(session->group);
590         session->group = NULL;
591
592         ok = nntp_group(session, group, num, first, last);
593         if (ok == NN_SUCCESS)
594                 session->group = g_strdup(group);
595
596         return ok;
597 }
598
599 #define PARSE_ONE_PARAM(p, srcp) \
600 { \
601         p = strchr(srcp, '\t'); \
602         if (!p) return NULL; \
603         else \
604                 *p++ = '\0'; \
605 }
606
607 static MsgInfo *news_parse_xover(const gchar *xover_str)
608 {
609         MsgInfo *msginfo;
610         gchar buf[NNTPBUFSIZE];
611         gchar *subject, *sender, *size, *line, *date, *msgid, *ref, *tmp;
612         gchar *p;
613         gint num, size_int, line_int;
614         gchar *xover_buf;
615
616         Xstrdup_a(xover_buf, xover_str, return NULL);
617
618         PARSE_ONE_PARAM(subject, xover_buf);
619         PARSE_ONE_PARAM(sender, subject);
620         PARSE_ONE_PARAM(date, sender);
621         PARSE_ONE_PARAM(msgid, date);
622         PARSE_ONE_PARAM(ref, msgid);
623         PARSE_ONE_PARAM(size, ref);
624         PARSE_ONE_PARAM(line, size);
625         /*
626          * PARSE_ONE_PARAM(xref, line);
627          *
628          * if we parse extra headers we should first examine the
629          * LIST OVERVIEW.FMT response from the server. See
630          * RFC2980 for details
631          */
632
633         tmp = strchr(line, '\t');
634         if (!tmp) tmp = strchr(line, '\r');
635         if (!tmp) tmp = strchr(line, '\n');
636         if (tmp) *tmp = '\0';
637
638         num = atoi(xover_str);
639         size_int = atoi(size);
640         line_int = atoi(line);
641
642         /* set MsgInfo */
643         msginfo = procmsg_msginfo_new();
644         msginfo->msgnum = num;
645         msginfo->size = size_int;
646
647         msginfo->date = g_strdup(date);
648         msginfo->date_t = procheader_date_parse(NULL, date, 0);
649
650         conv_unmime_header(buf, sizeof(buf), sender, NULL);
651         msginfo->from = g_strdup(buf);
652         msginfo->fromname = procheader_get_fromname(buf);
653
654         conv_unmime_header(buf, sizeof(buf), subject, NULL);
655         msginfo->subject = g_strdup(buf);
656
657         extract_parenthesis(msgid, '<', '>');
658         remove_space(msgid);
659         if (*msgid != '\0')
660                 msginfo->msgid = g_strdup(msgid);
661
662         msginfo->references = g_strdup(ref);
663         eliminate_parenthesis(ref, '(', ')');
664         if ((p = strrchr(ref, '<')) != NULL) {
665                 extract_parenthesis(p, '<', '>');
666                 remove_space(p);
667                 if (*p != '\0')
668                         msginfo->inreplyto = g_strdup(p);
669         }
670
671         /*
672         msginfo->xref = g_strdup(xref);
673         p = msginfo->xref+strlen(msginfo->xref) - 1;
674         while (*p == '\r' || *p == '\n') {
675                 *p = '\0';
676                 p--;
677         }
678         */
679
680         return msginfo;
681 }
682
683 static gchar *news_parse_xhdr(const gchar *xhdr_str, MsgInfo *msginfo)
684 {
685         gchar *p;
686         gchar *tmp;
687         gint num;
688
689         p = strchr(xhdr_str, ' ');
690         if (!p)
691                 return NULL;
692         else
693                 p++;
694
695         num = atoi(xhdr_str);
696         if (msginfo->msgnum != num) return NULL;
697
698         tmp = strchr(p, '\r');
699         if (!tmp) tmp = strchr(p, '\n');
700
701         if (tmp)
702                 return g_strndup(p, tmp - p);
703         else
704                 return g_strdup(p);
705 }
706
707 gint news_cancel_article(Folder * folder, MsgInfo * msginfo)
708 {
709         gchar * tmp;
710         FILE * tmpfp;
711         gchar buf[BUFFSIZE];
712
713         tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
714                               G_DIR_SEPARATOR, (gint)msginfo);
715         if (tmp == NULL)
716                 return -1;
717
718         if ((tmpfp = fopen(tmp, "wb")) == NULL) {
719                 FILE_OP_ERROR(tmp, "fopen");
720                 return -1;
721         }
722         if (change_file_mode_rw(tmpfp, tmp) < 0) {
723                 FILE_OP_ERROR(tmp, "chmod");
724                 g_warning("can't change file mode\n");
725         }
726         
727         fprintf(tmpfp, "From: %s\r\n", msginfo->from);
728         fprintf(tmpfp, "Newsgroups: %s\r\n", msginfo->newsgroups);
729         fprintf(tmpfp, "Subject: cmsg cancel <%s>\r\n", msginfo->msgid);
730         fprintf(tmpfp, "Control: cancel <%s>\r\n", msginfo->msgid);
731         fprintf(tmpfp, "Approved: %s\r\n", msginfo->from);
732         fprintf(tmpfp, "X-Cancelled-by: %s\r\n", msginfo->from);
733         get_rfc822_date(buf, sizeof(buf));
734         fprintf(tmpfp, "Date: %s\r\n", buf);
735         fprintf(tmpfp, "\r\n");
736         fprintf(tmpfp, "removed with sylpheed\r\n");
737
738         fclose(tmpfp);
739
740         news_post(folder, tmp);
741         remove(tmp);
742
743         g_free(tmp);
744
745         return 0;
746 }
747
748 static gchar *news_folder_get_path(Folder *folder)
749 {
750         gchar *folder_path;
751
752         g_return_val_if_fail(folder->account != NULL, NULL);
753
754         folder_path = g_strconcat(get_news_cache_dir(),
755                                   G_DIR_SEPARATOR_S,
756                                   folder->account->nntp_server,
757                                   NULL);
758         return folder_path;
759 }
760
761 gchar *news_item_get_path(Folder *folder, FolderItem *item)
762 {
763         gchar *folder_path, *path;
764
765         g_return_val_if_fail(folder != NULL, NULL);
766         g_return_val_if_fail(item != NULL, NULL);
767         folder_path = news_folder_get_path(folder);
768
769         g_return_val_if_fail(folder_path != NULL, NULL);
770         if (folder_path[0] == G_DIR_SEPARATOR) {
771                 if (item->path)
772                         path = g_strconcat(folder_path, G_DIR_SEPARATOR_S,
773                                            item->path, NULL);
774                 else
775                         path = g_strdup(folder_path);
776         } else {
777                 if (item->path)
778                         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
779                                            folder_path, G_DIR_SEPARATOR_S,
780                                            item->path, NULL);
781                 else
782                         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
783                                            folder_path, NULL);
784         }
785         g_free(folder_path);
786
787         return path;
788 }
789
790 gint news_get_num_list(Folder *folder, FolderItem *item, GSList **msgnum_list, gboolean *old_uids_valid)
791 {
792         NNTPSession *session;
793         gint i, ok, num, first, last, nummsgs = 0;
794         gchar *dir;
795
796         g_return_val_if_fail(item != NULL, -1);
797         g_return_val_if_fail(item->folder != NULL, -1);
798         g_return_val_if_fail(FOLDER_CLASS(folder) == &news_class, -1);
799
800         session = news_session_get(folder);
801         g_return_val_if_fail(session != NULL, -1);
802
803         *old_uids_valid = TRUE;
804
805         ok = news_select_group(session, item->path, &num, &first, &last);
806         if (ok != NN_SUCCESS) {
807                 log_warning(_("can't set group: %s\n"), item->path);
808                 return -1;
809         }
810
811         if (num <= 0) {
812                 remove_all_numbered_files(dir);
813                 return 0;
814         }
815
816         if(last < first) {
817                 log_warning(_("invalid article range: %d - %d\n"),
818                             first, last);
819                 return 0;
820         }
821
822         for(i = first; i <= last; i++) {
823                 *msgnum_list = g_slist_prepend(*msgnum_list, GINT_TO_POINTER(i));
824                 nummsgs++;
825         }
826
827         dir = folder_item_get_path(item);
828         debug_print("removing old messages from %d to %d in %s\n", first, last, dir);
829         remove_numbered_files(dir, 1, first - 1);
830         g_free(dir);
831
832         return nummsgs;
833 }
834
835 #define READ_TO_LISTEND(hdr) \
836         while (!(buf[0] == '.' && buf[1] == '\r')) { \
837                 if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) { \
838                         log_warning(_("error occurred while getting %s.\n"), hdr); \
839                         return msginfo; \
840                 } \
841         }
842
843 MsgInfo *news_get_msginfo(Folder *folder, FolderItem *item, gint num)
844 {
845         NNTPSession *session;
846         MsgInfo *msginfo = NULL;
847         gchar buf[NNTPBUFSIZE];
848
849         session = news_session_get(folder);
850         g_return_val_if_fail(session != NULL, NULL);
851         g_return_val_if_fail(item != NULL, NULL);
852         g_return_val_if_fail(item->folder != NULL, NULL);
853         g_return_val_if_fail(FOLDER_CLASS(item->folder) == &news_class, NULL);
854
855         log_message(_("getting xover %d in %s...\n"),
856                     num, item->path);
857         if (nntp_xover(session, num, num) != NN_SUCCESS) {
858                 log_warning(_("can't get xover\n"));
859                 return NULL;
860         }
861         
862         if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
863                 log_warning(_("error occurred while getting xover.\n"));
864                 return NULL;
865         }
866         
867         msginfo = news_parse_xover(buf);
868         if (!msginfo) {
869                 log_warning(_("invalid xover line: %s\n"), buf);
870         }
871
872         READ_TO_LISTEND("xover");
873
874         if(!msginfo)
875                 return NULL;
876         
877         msginfo->folder = item;
878         msginfo->flags.perm_flags = MSG_NEW|MSG_UNREAD;
879         msginfo->flags.tmp_flags = MSG_NEWS;
880         msginfo->newsgroups = g_strdup(item->path);
881
882         if (nntp_xhdr(session, "to", num, num) != NN_SUCCESS) {
883                 log_warning(_("can't get xhdr\n"));
884                 return msginfo;
885         }
886
887         if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
888                 log_warning(_("error occurred while getting xhdr.\n"));
889                 return msginfo;
890         }
891
892         msginfo->to = news_parse_xhdr(buf, msginfo);
893
894         READ_TO_LISTEND("xhdr (to)");
895
896         if (nntp_xhdr(session, "cc", num, num) != NN_SUCCESS) {
897                 log_warning(_("can't get xhdr\n"));
898                 return msginfo;
899         }
900
901         if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
902                 log_warning(_("error occurred while getting xhdr.\n"));
903                 return msginfo;
904         }
905
906         msginfo->cc = news_parse_xhdr(buf, msginfo);
907
908         READ_TO_LISTEND("xhdr (cc)");
909
910         return msginfo;
911 }
912
913 static GSList *news_get_msginfos_for_range(NNTPSession *session, FolderItem *item, guint begin, guint end)
914 {
915         gchar buf[NNTPBUFSIZE];
916         GSList *newlist = NULL;
917         GSList *llast = NULL;
918         MsgInfo *msginfo;
919         guint count = 0, lines = (end - begin + 2) * 3;
920
921         g_return_val_if_fail(session != NULL, NULL);
922         g_return_val_if_fail(item != NULL, NULL);
923
924         log_message(_("getting xover %d - %d in %s...\n"),
925                     begin, end, item->path);
926         if (nntp_xover(session, begin, end) != NN_SUCCESS) {
927                 log_warning(_("can't get xover\n"));
928                 return NULL;
929         }
930
931         for (;;) {
932                 if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
933                         log_warning(_("error occurred while getting xover.\n"));
934                         return newlist;
935                 }
936                 count++;
937                 progressindicator_set_percentage
938                         (PROGRESS_TYPE_NETWORK,
939                          session->fetch_base_percentage +
940                          (((gfloat) count) / ((gfloat) lines)) * session->fetch_total_percentage);
941
942                 if (buf[0] == '.' && buf[1] == '\r') break;
943
944                 msginfo = news_parse_xover(buf);
945                 if (!msginfo) {
946                         log_warning(_("invalid xover line: %s\n"), buf);
947                         continue;
948                 }
949
950                 msginfo->folder = item;
951                 msginfo->flags.perm_flags = MSG_NEW|MSG_UNREAD;
952                 msginfo->flags.tmp_flags = MSG_NEWS;
953                 msginfo->newsgroups = g_strdup(item->path);
954
955                 if (!newlist)
956                         llast = newlist = g_slist_append(newlist, msginfo);
957                 else {
958                         llast = g_slist_append(llast, msginfo);
959                         llast = llast->next;
960                 }
961         }
962
963         if (nntp_xhdr(session, "to", begin, end) != NN_SUCCESS) {
964                 log_warning(_("can't get xhdr\n"));
965                 return newlist;
966         }
967
968         llast = newlist;
969
970         for (;;) {
971                 if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
972                         log_warning(_("error occurred while getting xhdr.\n"));
973                         return newlist;
974                 }
975                 count++;
976                 progressindicator_set_percentage
977                         (PROGRESS_TYPE_NETWORK,
978                          session->fetch_base_percentage +
979                          (((gfloat) count) / ((gfloat) lines)) * session->fetch_total_percentage);
980
981                 if (buf[0] == '.' && buf[1] == '\r') break;
982                 if (!llast) {
983                         g_warning("llast == NULL\n");
984                         continue;
985                 }
986
987                 msginfo = (MsgInfo *)llast->data;
988                 msginfo->to = news_parse_xhdr(buf, msginfo);
989
990                 llast = llast->next;
991         }
992
993         if (nntp_xhdr(session, "cc", begin, end) != NN_SUCCESS) {
994                 log_warning(_("can't get xhdr\n"));
995                 return newlist;
996         }
997
998         llast = newlist;
999
1000         for (;;) {
1001                 if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
1002                         log_warning(_("error occurred while getting xhdr.\n"));
1003                         return newlist;
1004                 }
1005                 count++;
1006                 progressindicator_set_percentage
1007                         (PROGRESS_TYPE_NETWORK,
1008                          session->fetch_base_percentage +
1009                          (((gfloat) count) / ((gfloat) lines)) * session->fetch_total_percentage);
1010
1011                 if (buf[0] == '.' && buf[1] == '\r') break;
1012                 if (!llast) {
1013                         g_warning("llast == NULL\n");
1014                         continue;
1015                 }
1016
1017                 msginfo = (MsgInfo *)llast->data;
1018                 msginfo->cc = news_parse_xhdr(buf, msginfo);
1019
1020                 llast = llast->next;
1021         }
1022
1023         return newlist;
1024 }
1025
1026 GSList *news_get_msginfos(Folder *folder, FolderItem *item, GSList *msgnum_list)
1027 {
1028         NNTPSession *session;
1029         GSList *elem, *msginfo_list = NULL, *tmp_msgnum_list, *tmp_msginfo_list;
1030         guint first, last, next;
1031         guint tofetch, fetched;
1032         
1033         g_return_val_if_fail(folder != NULL, NULL);
1034         g_return_val_if_fail(FOLDER_CLASS(folder) == &news_class, NULL);
1035         g_return_val_if_fail(msgnum_list != NULL, NULL);
1036         g_return_val_if_fail(item != NULL, NULL);
1037         
1038         session = news_session_get(folder);
1039         g_return_val_if_fail(session != NULL, NULL);
1040
1041         tmp_msgnum_list = g_slist_copy(msgnum_list);
1042         tmp_msgnum_list = g_slist_sort(tmp_msgnum_list, g_int_compare);
1043
1044         progressindicator_start(PROGRESS_TYPE_NETWORK);
1045         tofetch = g_slist_length(tmp_msgnum_list);
1046         fetched = 0;
1047
1048         first = GPOINTER_TO_INT(tmp_msgnum_list->data);
1049         last = first;
1050         for(elem = g_slist_next(tmp_msgnum_list); elem != NULL; elem = g_slist_next(elem)) {
1051                 next = GPOINTER_TO_INT(elem->data);
1052                 if(next != (last + 1)) {
1053                         session->fetch_base_percentage = ((gfloat) fetched) / ((gfloat) tofetch);
1054                         session->fetch_total_percentage = ((gfloat) (last - first + 1)) / ((gfloat) tofetch);
1055                         tmp_msginfo_list = news_get_msginfos_for_range(session, item, first, last);
1056                         msginfo_list = g_slist_concat(msginfo_list, tmp_msginfo_list);
1057                         fetched = last - first + 1;
1058                         first = next;
1059                 }
1060                 last = next;
1061         }
1062         session->fetch_base_percentage = ((gfloat) fetched) / ((gfloat) tofetch);
1063         session->fetch_total_percentage = ((gfloat) (last - first + 1)) / ((gfloat) tofetch);
1064         tmp_msginfo_list = news_get_msginfos_for_range(session, item, first, last);
1065         msginfo_list = g_slist_concat(msginfo_list, tmp_msginfo_list);
1066
1067         g_slist_free(tmp_msgnum_list);
1068         
1069         progressindicator_stop(PROGRESS_TYPE_NETWORK);
1070
1071         return msginfo_list;
1072 }
1073
1074 gboolean news_scan_required(Folder *folder, FolderItem *item)
1075 {
1076         return TRUE;
1077 }