0d4d6fa71a891762453a654e124ca269c2d413c5
[claws.git] / src / news.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2009 Hiroyuki Yamamoto and the Claws Mail team
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 3 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, see <http://www.gnu.org/licenses/>.
17  * 
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #ifdef HAVE_LIBETPAN
25
26 #include "defs.h"
27
28 #include <glib.h>
29 #include <glib/gi18n.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include <stdlib.h>
33 #include <dirent.h>
34 #include <unistd.h>
35 #include <time.h>
36 #include <libetpan/libetpan.h>
37
38 #include "nntp-thread.h"
39 #include "news.h"
40 #include "news_gtk.h"
41 #include "socket.h"
42 #include "recv.h"
43 #include "procmsg.h"
44 #include "procheader.h"
45 #include "folder.h"
46 #include "session.h"
47 #include "statusbar.h"
48 #include "codeconv.h"
49 #include "utils.h"
50 #include "prefs_common.h"
51 #include "prefs_account.h"
52 #include "inputdialog.h"
53 #include "log.h"
54 #include "progressindicator.h"
55 #include "remotefolder.h"
56 #include "alertpanel.h"
57 #include "inc.h"
58 #ifdef USE_GNUTLS
59 #  include "ssl.h"
60 #endif
61
62 #define NNTP_PORT       119
63 #ifdef USE_GNUTLS
64 #define NNTPS_PORT      563
65 #endif
66
67 typedef struct _NewsFolder      NewsFolder;
68 typedef struct _NewsSession     NewsSession;
69
70 #define NEWS_FOLDER(obj)        ((NewsFolder *)obj)
71 #define NEWS_SESSION(obj)       ((NewsSession *)obj)
72
73 struct _NewsFolder
74 {
75         RemoteFolder rfolder;
76
77         gboolean use_auth;
78         gboolean lock_count;
79         guint refcnt;
80 };
81
82 struct _NewsSession
83 {
84         Session session;
85
86         gchar *group;
87 };
88
89 static void news_folder_init(Folder *folder, const gchar *name,
90                              const gchar *path);
91
92 static Folder   *news_folder_new        (const gchar    *name,
93                                          const gchar    *folder);
94 static void      news_folder_destroy    (Folder         *folder);
95
96 static gchar *news_fetch_msg            (Folder         *folder,
97                                          FolderItem     *item,
98                                          gint            num);
99 static void news_remove_cached_msg      (Folder         *folder, 
100                                          FolderItem     *item, 
101                                          MsgInfo        *msginfo);
102 #ifdef USE_GNUTLS
103 static Session *news_session_new         (Folder        *folder,
104                                           const gchar   *server,
105                                           gushort        port,
106                                           const gchar   *userid,
107                                           const gchar   *passwd,
108                                           SSLType        ssl_type);
109 #else
110 static Session *news_session_new         (Folder        *folder,
111                                           const gchar   *server,
112                                           gushort        port,
113                                           const gchar   *userid,
114                                           const gchar   *passwd);
115 #endif
116
117 static gint news_get_article             (Folder        *folder,
118                                           gint           num,
119                                           gchar         *filename);
120
121 static gint news_select_group            (Folder        *folder,
122                                           const gchar   *group,
123                                           gint          *num,
124                                           gint          *first,
125                                           gint          *last);
126 static MsgInfo *news_parse_xover         (struct newsnntp_xover_resp_item *item);
127 static gint news_get_num_list                    (Folder        *folder, 
128                                           FolderItem    *item,
129                                           GSList       **list,
130                                           gboolean      *old_uids_valid);
131 static MsgInfo *news_get_msginfo                 (Folder        *folder, 
132                                           FolderItem    *item,
133                                           gint           num);
134 static GSList *news_get_msginfos                 (Folder        *folder,
135                                           FolderItem    *item,
136                                           GSList        *msgnum_list);
137 static gboolean news_scan_required               (Folder        *folder,
138                                           FolderItem    *item);
139
140 static gchar *news_folder_get_path       (Folder        *folder);
141 static gchar *news_item_get_path                 (Folder        *folder,
142                                           FolderItem    *item);
143 static void news_synchronise             (FolderItem    *item, gint days);
144 static int news_remove_msg               (Folder        *folder, 
145                                           FolderItem    *item, 
146                                           gint           msgnum);
147 static gint news_rename_folder           (Folder *folder,
148                                           FolderItem *item,
149                                           const gchar *name);
150 static gint news_remove_folder           (Folder        *folder,
151                                           FolderItem    *item);
152 static FolderClass news_class;
153
154 FolderClass *news_get_class(void)
155 {
156         if (news_class.idstr == NULL) {
157                 news_class.type = F_NEWS;
158                 news_class.idstr = "news";
159                 news_class.uistr = "News";
160
161                 /* Folder functions */
162                 news_class.new_folder = news_folder_new;
163                 news_class.destroy_folder = news_folder_destroy;
164
165                 /* FolderItem functions */
166                 news_class.item_get_path = news_item_get_path;
167                 news_class.get_num_list = news_get_num_list;
168                 news_class.scan_required = news_scan_required;
169                 news_class.rename_folder = news_rename_folder;
170                 news_class.remove_folder = news_remove_folder;
171
172                 /* Message functions */
173                 news_class.get_msginfo = news_get_msginfo;
174                 news_class.get_msginfos = news_get_msginfos;
175                 news_class.fetch_msg = news_fetch_msg;
176                 news_class.synchronise = news_synchronise;
177                 news_class.remove_msg = news_remove_msg;
178                 news_class.remove_cached_msg = news_remove_cached_msg;
179         };
180
181         return &news_class;
182 }
183
184 guint nntp_folder_get_refcnt(Folder *folder)
185 {
186         return ((NewsFolder *)folder)->refcnt;
187 }
188
189 void nntp_folder_ref(Folder *folder)
190 {
191         ((NewsFolder *)folder)->refcnt++;
192 }
193
194 void nntp_folder_unref(Folder *folder)
195 {
196         if (((NewsFolder *)folder)->refcnt > 0)
197                 ((NewsFolder *)folder)->refcnt--;
198 }
199
200 static int news_remove_msg               (Folder        *folder, 
201                                           FolderItem    *item, 
202                                           gint           msgnum)
203 {
204         gchar *path, *filename;
205
206         cm_return_val_if_fail(folder != NULL, -1);
207         cm_return_val_if_fail(item != NULL, -1);
208
209         path = folder_item_get_path(item);
210         if (!is_dir_exist(path))
211                 make_dir_hier(path);
212         
213         filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(msgnum), NULL);
214         g_free(path);
215         claws_unlink(filename);
216         g_free(filename);
217         return 0;
218 }
219
220 static void news_folder_lock(NewsFolder *folder)
221 {
222         folder->lock_count++;
223 }
224
225 static void news_folder_unlock(NewsFolder *folder)
226 {
227         if (folder->lock_count > 0)
228                 folder->lock_count--;
229 }
230
231 int news_folder_locked(Folder *folder)
232 {
233         if (folder == NULL)
234                 return 0;
235
236         return NEWS_FOLDER(folder)->lock_count;
237 }
238
239 static Folder *news_folder_new(const gchar *name, const gchar *path)
240 {
241         Folder *folder;
242
243         folder = (Folder *)g_new0(NewsFolder, 1);
244         folder->klass = &news_class;
245         news_folder_init(folder, name, path);
246
247         return folder;
248 }
249
250 static void news_folder_destroy(Folder *folder)
251 {
252         gchar *dir;
253
254         while (nntp_folder_get_refcnt(folder) > 0)
255                 gtk_main_iteration();
256
257         dir = news_folder_get_path(folder);
258         if (is_dir_exist(dir))
259                 remove_dir_recursive(dir);
260         g_free(dir);
261
262         nntp_done(folder);
263         folder_remote_folder_destroy(REMOTE_FOLDER(folder));
264 }
265
266 static void news_folder_init(Folder *folder, const gchar *name,
267                              const gchar *path)
268 {
269         folder_remote_folder_init(folder, name, path);
270 }
271
272 static void news_session_destroy(Session *session)
273 {
274         NewsSession *news_session = NEWS_SESSION(session);
275
276         cm_return_if_fail(session != NULL);
277
278         if (news_session->group)
279                 g_free(news_session->group);
280 }
281
282 #ifdef USE_GNUTLS
283 static Session *news_session_new(Folder *folder, const gchar *server, gushort port,
284                                  const gchar *userid, const gchar *passwd,
285                                  SSLType ssl_type)
286 #else
287 static Session *news_session_new(Folder *folder, const gchar *server, gushort port,
288                                  const gchar *userid, const gchar *passwd)
289 #endif
290 {
291         NewsSession *session;
292         int r = 0;
293         cm_return_val_if_fail(server != NULL, NULL);
294
295         log_message(LOG_PROTOCOL, _("creating NNTP connection to %s:%d ...\n"), server, port);
296
297         session = g_new0(NewsSession, 1);
298         session_init(SESSION(session), folder->account, FALSE);
299         SESSION(session)->type             = SESSION_NEWS;
300         SESSION(session)->server           = g_strdup(server);
301         SESSION(session)->sock             = NULL;
302         SESSION(session)->destroy          = news_session_destroy;
303         
304         nntp_init(folder);
305
306 #ifdef USE_GNUTLS
307         if (ssl_type != SSL_NONE)
308                 r = nntp_threaded_connect_ssl(folder, server, port);
309         else
310 #endif
311                 r = nntp_threaded_connect(folder, server, port);
312         
313         if (r != NEWSNNTP_NO_ERROR) {
314                 log_error(LOG_PROTOCOL, _("Error logging in to %s:%d ...\n"), server, port);
315                 session_destroy(SESSION(session));
316                 return NULL;
317         }
318         
319         return SESSION(session);
320 }
321
322 static Session *news_session_new_for_folder(Folder *folder)
323 {
324         Session *session;
325         PrefsAccount *ac;
326         const gchar *userid = NULL;
327         gchar *passwd = NULL;
328         gushort port;
329
330         cm_return_val_if_fail(folder != NULL, NULL);
331         cm_return_val_if_fail(folder->account != NULL, NULL);
332
333         ac = folder->account;
334         if (ac->use_nntp_auth && ac->userid && ac->userid[0]) {
335                 userid = ac->userid;
336                 if (ac->passwd && ac->passwd[0])
337                         passwd = g_strdup(ac->passwd);
338                 else
339                         passwd = input_dialog_query_password_keep(ac->nntp_server,
340                                                                   userid,
341                                                                   &(ac->session_passwd));
342         }
343
344 #ifdef USE_GNUTLS
345         port = ac->set_nntpport ? ac->nntpport
346                 : ac->ssl_nntp ? NNTPS_PORT : NNTP_PORT;
347         session = news_session_new(folder, ac->nntp_server, port, userid, passwd,
348                                    ac->ssl_nntp);
349 #else
350         if (ac->ssl_nntp != SSL_NONE) {
351                 if (alertpanel_full(_("Insecure connection"),
352                         _("This connection is configured to be secured "
353                           "using SSL, but SSL is not available in this "
354                           "build of Claws Mail. \n\n"
355                           "Do you want to continue connecting to this "
356                           "server? The communication would not be "
357                           "secure."),
358                           GTK_STOCK_CANCEL, _("Con_tinue connecting"), 
359                           NULL, FALSE, NULL, ALERT_WARNING,
360                           G_ALERTDEFAULT) != G_ALERTALTERNATE)
361                         return NULL;
362         }
363         port = ac->set_nntpport ? ac->nntpport : NNTP_PORT;
364         session = news_session_new(folder, ac->nntp_server, port, userid, passwd);
365 #endif
366
367         if ((session != NULL) && ac->use_nntp_auth) { /* FIXME:  && ac->use_nntp_auth_onconnect */
368                 if (nntp_threaded_login(folder, userid, passwd) !=
369                         NEWSNNTP_NO_ERROR) {
370                         log_error(LOG_PROTOCOL, _("Error authenticating to %s:%d ...\n"), ac->nntp_server, port);
371                         session_destroy(SESSION(session));
372                         g_free(passwd);
373                         if (ac->session_passwd) {
374                                 g_free(ac->session_passwd);
375                                 ac->session_passwd = NULL;
376                         }
377                         return NULL;
378                 }
379         }
380         g_free(passwd);
381
382         return session;
383 }
384
385 static NewsSession *news_session_get(Folder *folder)
386 {
387         RemoteFolder *rfolder = REMOTE_FOLDER(folder);
388         struct tm lt;
389         int r;
390         
391         cm_return_val_if_fail(folder != NULL, NULL);
392         cm_return_val_if_fail(FOLDER_CLASS(folder) == &news_class, NULL);
393         cm_return_val_if_fail(folder->account != NULL, NULL);
394
395         if (prefs_common.work_offline && 
396             !inc_offline_should_override(FALSE,
397                 _("Claws Mail needs network access in order "
398                   "to access the News server."))) {
399                 return NULL;
400         }
401
402         if (!rfolder->session) {
403                 rfolder->session = news_session_new_for_folder(folder);
404                 return NEWS_SESSION(rfolder->session);
405         }
406
407         if (time(NULL) - rfolder->session->last_access_time <
408                 SESSION_TIMEOUT_INTERVAL) {
409                 return NEWS_SESSION(rfolder->session);
410         }
411
412         if ((r = nntp_threaded_date(folder, &lt)) != NEWSNNTP_NO_ERROR) {
413                 if (r != NEWSNNTP_ERROR_COMMAND_NOT_SUPPORTED &&
414                     r != NEWSNNTP_ERROR_COMMAND_NOT_UNDERSTOOD) {
415                         log_warning(LOG_PROTOCOL, _("NNTP connection to %s:%d has been"
416                               " disconnected. Reconnecting...\n"),
417                             folder->account->nntp_server,
418                             folder->account->set_nntpport ?
419                             folder->account->nntpport : NNTP_PORT);
420                         session_destroy(rfolder->session);
421                         rfolder->session = news_session_new_for_folder(folder);
422                 }
423         }
424         
425         if (rfolder->session)
426                 session_set_access_time(rfolder->session);
427
428         return NEWS_SESSION(rfolder->session);
429 }
430
431 static void news_remove_cached_msg(Folder *folder, FolderItem *item, MsgInfo *msginfo)
432 {
433         gchar *path, *filename;
434
435         path = folder_item_get_path(item);
436
437         if (!is_dir_exist(path)) {
438                 g_free(path);
439                 return;
440         }
441
442         filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(msginfo->msgnum), NULL);
443         g_free(path);
444
445         if (is_file_exist(filename)) {
446                 claws_unlink(filename);
447         }
448         g_free(filename);
449 }
450
451 static gchar *news_fetch_msg(Folder *folder, FolderItem *item, gint num)
452 {
453         gchar *path, *filename;
454         NewsSession *session;
455         gint ok;
456
457         cm_return_val_if_fail(folder != NULL, NULL);
458         cm_return_val_if_fail(item != NULL, NULL);
459
460         path = folder_item_get_path(item);
461         if (!is_dir_exist(path))
462                 make_dir_hier(path);
463         filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(num), NULL);
464         g_free(path);
465
466         if (is_file_exist(filename)) {
467                 debug_print("article %d has been already cached.\n", num);
468                 return filename;
469         }
470
471         session = news_session_get(folder);
472         if (!session) {
473                 g_free(filename);
474                 return NULL;
475         }
476
477         ok = news_select_group(folder, item->path, NULL, NULL, NULL);
478         if (ok != NEWSNNTP_NO_ERROR) {
479                 if (ok == NEWSNNTP_ERROR_STREAM) {
480                         session_destroy(SESSION(session));
481                         REMOTE_FOLDER(folder)->session = NULL;
482                 }
483                 g_free(filename);
484                 return NULL;
485         }
486
487         debug_print("getting article %d...\n", num);
488         ok = news_get_article(folder,
489                               num, filename);
490         if (ok != NEWSNNTP_NO_ERROR) {
491                 g_warning("can't read article %d\n", num);
492                 if (ok == NEWSNNTP_ERROR_STREAM) {
493                         session_destroy(SESSION(session));
494                         REMOTE_FOLDER(folder)->session = NULL;
495                 }
496                 g_free(filename);
497                 return NULL;
498         }
499         GTK_EVENTS_FLUSH();
500         return filename;
501 }
502
503 static NewsGroupInfo *news_group_info_new(const gchar *name,
504                                           gint first, gint last, gchar type)
505 {
506         NewsGroupInfo *ginfo;
507
508         ginfo = g_new(NewsGroupInfo, 1);
509         ginfo->name = g_strdup(name);
510         ginfo->first = first;
511         ginfo->last = last;
512         ginfo->type = type;
513
514         return ginfo;
515 }
516
517 static void news_group_info_free(NewsGroupInfo *ginfo)
518 {
519         g_free(ginfo->name);
520         g_free(ginfo);
521 }
522
523 static gint news_group_info_compare(NewsGroupInfo *ginfo1,
524                                     NewsGroupInfo *ginfo2)
525 {
526         return g_ascii_strcasecmp(ginfo1->name, ginfo2->name);
527 }
528
529 GSList *news_get_group_list(Folder *folder)
530 {
531         gchar *path, *filename;
532         FILE *fp;
533         GSList *list = NULL;
534         GSList *last = NULL;
535         gchar buf[BUFFSIZE];
536
537         cm_return_val_if_fail(folder != NULL, NULL);
538         cm_return_val_if_fail(FOLDER_CLASS(folder) == &news_class, NULL);
539
540         path = folder_item_get_path(FOLDER_ITEM(folder->node->data));
541         if (!is_dir_exist(path))
542                 make_dir_hier(path);
543         filename = g_strconcat(path, G_DIR_SEPARATOR_S, NEWSGROUP_LIST, NULL);
544         g_free(path);
545
546         if ((fp = g_fopen(filename, "rb")) == NULL) {
547                 NewsSession *session;
548                 gint ok;
549                 clist *grouplist = NULL;
550                 clistiter *cur;
551                 fp = g_fopen(filename, "wb");
552                 
553                 if (!fp) {
554                         g_free(filename);
555                         return NULL;
556                 }
557                 session = news_session_get(folder);
558                 if (!session) {
559                         fclose(fp);
560                         g_free(filename);
561                         return NULL;
562                 }
563
564                 ok = nntp_threaded_list(folder, &grouplist);
565                 
566                 if (ok != NEWSNNTP_NO_ERROR) {
567                         if (ok == NEWSNNTP_ERROR_STREAM) {
568                                 session_destroy(SESSION(session));
569                                 REMOTE_FOLDER(folder)->session = NULL;
570                         }
571                         fclose(fp);
572                         g_free(filename);
573                         return NULL;
574                 }
575                 
576                 if (grouplist) {
577                         for (cur = clist_begin(grouplist); cur; cur = clist_next(cur)) {
578                                 struct newsnntp_group_info *info = (struct newsnntp_group_info *)
579                                                                         clist_content(cur);
580                                 if (fprintf(fp, "%s %d %d %c\n",
581                                         info->grp_name,
582                                         info->grp_last,
583                                         info->grp_first,
584                                         info->grp_type) < 0) {
585                                         log_error(LOG_PROTOCOL, ("Can't write newsgroup list\n"));
586                                         session_destroy(SESSION(session));
587                                         REMOTE_FOLDER(folder)->session = NULL;
588                                         fclose(fp);
589                                         g_free(filename);
590                                         newsnntp_list_free(grouplist);
591                                         return NULL;
592                                 }
593                         }
594                         newsnntp_list_free(grouplist);
595                 }
596                 if (fclose(fp) == EOF) {
597                         log_error(LOG_PROTOCOL, ("Can't write newsgroup list\n"));
598                         session_destroy(SESSION(session));
599                         REMOTE_FOLDER(folder)->session = NULL;
600                         g_free(filename);
601                         return NULL;
602                 }
603
604                 if ((fp = g_fopen(filename, "rb")) == NULL) {
605                         FILE_OP_ERROR(filename, "fopen");
606                         g_free(filename);
607                         return NULL;
608                 }
609         }
610
611         while (fgets(buf, sizeof(buf), fp) != NULL) {
612                 gchar *p = buf;
613                 gchar *name;
614                 gint last_num;
615                 gint first_num;
616                 gchar type;
617                 NewsGroupInfo *ginfo;
618
619                 p = strchr(p, ' ');
620                 if (!p) continue;
621                 *p = '\0';
622                 p++;
623                 name = buf;
624
625                 if (sscanf(p, "%d %d %c", &last_num, &first_num, &type) < 3)
626                         continue;
627
628                 ginfo = news_group_info_new(name, first_num, last_num, type);
629
630                 if (!last)
631                         last = list = g_slist_append(NULL, ginfo);
632                 else {
633                         last = g_slist_append(last, ginfo);
634                         last = last->next;
635                 }
636         }
637
638         fclose(fp);
639         g_free(filename);
640
641         list = g_slist_sort(list, (GCompareFunc)news_group_info_compare);
642
643         return list;
644 }
645
646 void news_group_list_free(GSList *group_list)
647 {
648         GSList *cur;
649
650         if (!group_list) return;
651
652         for (cur = group_list; cur != NULL; cur = cur->next)
653                 news_group_info_free((NewsGroupInfo *)cur->data);
654         g_slist_free(group_list);
655 }
656
657 void news_remove_group_list_cache(Folder *folder)
658 {
659         gchar *path, *filename;
660
661         cm_return_if_fail(folder != NULL);
662         cm_return_if_fail(FOLDER_CLASS(folder) == &news_class);
663
664         path = folder_item_get_path(FOLDER_ITEM(folder->node->data));
665         filename = g_strconcat(path, G_DIR_SEPARATOR_S, NEWSGROUP_LIST, NULL);
666         g_free(path);
667
668         if (is_file_exist(filename)) {
669                 if (remove(filename) < 0)
670                         FILE_OP_ERROR(filename, "remove");
671         }
672         g_free(filename);
673 }
674
675 gint news_post(Folder *folder, const gchar *file)
676 {
677         gint ok;
678         char *contents = file_read_to_str_no_recode(file);
679         NewsSession *session;
680
681         cm_return_val_if_fail(folder != NULL, -1);
682         cm_return_val_if_fail(FOLDER_CLASS(folder) == &news_class, -1);
683         cm_return_val_if_fail(contents != NULL, -1);
684         
685         session = news_session_get(folder);
686         if (!session)  {
687                 g_free(contents);
688                 return -1;
689         }
690         
691         ok = nntp_threaded_post(folder, contents, strlen(contents));
692
693         if (ok != NEWSNNTP_NO_ERROR && ok != NEWSNNTP_ERROR_STREAM) {
694                 ok = nntp_threaded_mode_reader(folder);
695                 if (ok == NEWSNNTP_NO_ERROR)
696                         ok = nntp_threaded_post(folder, contents, strlen(contents));
697         }
698         g_free(contents);
699
700         if (ok == NEWSNNTP_ERROR_STREAM) {
701                 session_destroy(SESSION(session));
702                 REMOTE_FOLDER(folder)->session = NULL;
703         }
704
705         return (ok == NEWSNNTP_NO_ERROR ? 0 : -1);
706 }
707
708 static gint news_get_article(Folder *folder, gint num, gchar *filename)
709 {
710         size_t len;
711         char *result = NULL;
712         int r;
713         
714         r = nntp_threaded_article(folder, num, &result, &len);
715         
716         if (r == NEWSNNTP_NO_ERROR) {
717                 if (str_write_to_file(result, filename) < 0)
718                         return -1;
719                 g_free(result);
720         }
721         
722         return r;
723 }
724
725 /**
726  * news_select_group:
727  * @session: Active NNTP session.
728  * @group: Newsgroup name.
729  * @num: Estimated number of articles.
730  * @first: First article number.
731  * @last: Last article number.
732  *
733  * Select newsgroup @group with the GROUP command if it is not already
734  * selected in @session, or article numbers need to be returned.
735  *
736  * Return value: NNTP result code.
737  **/
738 static gint news_select_group(Folder *folder, const gchar *group,
739                               gint *num, gint *first, gint *last)
740 {
741         gint ok;
742         gint num_, first_, last_;
743         struct newsnntp_group_info *info = NULL;
744         NewsSession *session = NEWS_SESSION(news_session_get(folder));
745
746         cm_return_val_if_fail(session != NULL, -1);
747         
748         if (!num || !first || !last) {
749                 if (session->group && g_ascii_strcasecmp(session->group, group) == 0)
750                         return NEWSNNTP_NO_ERROR;
751                 num = &num_;
752                 first = &first_;
753                 last = &last_;
754         }
755
756         g_free(session->group);
757         session->group = NULL;
758
759         ok = nntp_threaded_group(folder, group, &info);
760         
761         if (ok != NEWSNNTP_NO_ERROR && 
762             ok != NEWSNNTP_ERROR_STREAM && 
763             ok != NEWSNNTP_WARNING_REQUEST_AUTHORIZATION_USERNAME) {
764                 ok = nntp_threaded_mode_reader(folder);
765                 if (ok == NEWSNNTP_NO_ERROR)
766                         ok = nntp_threaded_group(folder, group, &info);
767         }
768
769         if (ok == NEWSNNTP_NO_ERROR && info) {
770                 session->group = g_strdup(group);
771                 *num = info->grp_first;
772                 *first = info->grp_first;
773                 *last = info->grp_last;
774                 newsnntp_group_free(info);
775         } else {
776                 log_warning(LOG_PROTOCOL, _("couldn't select group: %s\n"), group);
777         }
778         return ok;
779 }
780
781 static MsgInfo *news_parse_xover(struct newsnntp_xover_resp_item *item)
782 {
783         MsgInfo *msginfo;
784
785         /* set MsgInfo */
786         msginfo = procmsg_msginfo_new();
787         msginfo->msgnum = item->ovr_article;
788         msginfo->size = item->ovr_size;
789
790         msginfo->date = g_strdup(item->ovr_date);
791         msginfo->date_t = procheader_date_parse(NULL, item->ovr_date, 0);
792
793         msginfo->from = conv_unmime_header(item->ovr_author, NULL);
794         msginfo->fromname = procheader_get_fromname(msginfo->from);
795
796         msginfo->subject = conv_unmime_header(item->ovr_subject, NULL);
797
798         remove_return(msginfo->from);
799         remove_return(msginfo->fromname);
800         remove_return(msginfo->subject);
801
802         if (item->ovr_message_id) {
803                 gchar *tmp = g_strdup(item->ovr_message_id);
804                 extract_parenthesis(tmp, '<', '>');
805                 remove_space(tmp);
806                 if (*tmp != '\0')
807                         msginfo->msgid = g_strdup(tmp);
808                 g_free(tmp);
809         }                        
810
811         /* FIXME: this is a quick fix; references' meaning was changed
812          * into having the actual list of references in the References: header.
813          * We need a GSList here, so msginfo_free() and msginfo_copy() can do 
814          * their things properly. */ 
815         if (item->ovr_references && *(item->ovr_references)) {   
816                 gchar **ref_tokens = g_strsplit(item->ovr_references, " ", -1);
817                 guint i = 0;
818                 char *tmp;
819                 char *p;
820                 while (ref_tokens[i]) {
821                         gchar *cur_ref = ref_tokens[i];
822                         msginfo->references = references_list_append(msginfo->references, 
823                                         cur_ref);
824                         i++;
825                 }
826                 g_strfreev(ref_tokens);
827                 
828                 tmp = g_strdup(item->ovr_references);
829                 eliminate_parenthesis(tmp, '(', ')');
830                 if ((p = strrchr(tmp, '<')) != NULL) {
831                         extract_parenthesis(p, '<', '>');
832                         remove_space(p);
833                         if (*p != '\0')
834                                 msginfo->inreplyto = g_strdup(p);
835                 }
836                 g_free(tmp);
837         } 
838
839         return msginfo;
840 }
841
842 gint news_cancel_article(Folder * folder, MsgInfo * msginfo)
843 {
844         gchar * tmp;
845         FILE * tmpfp;
846         gchar buf[BUFFSIZE];
847
848         tmp = g_strdup_printf("%s%ccancel%p", get_tmp_dir(),
849                               G_DIR_SEPARATOR, msginfo);
850         if (tmp == NULL)
851                 return -1;
852
853         if ((tmpfp = g_fopen(tmp, "wb")) == NULL) {
854                 FILE_OP_ERROR(tmp, "fopen");
855                 return -1;
856         }
857         if (change_file_mode_rw(tmpfp, tmp) < 0) {
858                 FILE_OP_ERROR(tmp, "chmod");
859                 g_warning("can't change file mode\n");
860         }
861         
862         get_rfc822_date(buf, sizeof(buf));
863         if (fprintf(tmpfp, "From: %s\r\n"
864                        "Newsgroups: %s\r\n"
865                        "Subject: cmsg cancel <%s>\r\n"
866                        "Control: cancel <%s>\r\n"
867                        "Approved: %s\r\n"
868                        "X-Cancelled-by: %s\r\n"
869                        "Date: %s\r\n"
870                        "\r\n"
871                        "removed with Claws Mail\r\n",
872                        msginfo->from,
873                        msginfo->newsgroups,
874                        msginfo->msgid,
875                        msginfo->msgid,
876                        msginfo->from,
877                        msginfo->from,
878                        buf) < 0) {
879                 FILE_OP_ERROR(tmp, "fprintf");
880                 fclose(tmpfp);
881                 claws_unlink(tmp);
882                 g_free(tmp);
883                 return -1;
884         }
885
886         if (fclose(tmpfp) == EOF) {
887                 FILE_OP_ERROR(tmp, "fclose");
888                 claws_unlink(tmp);
889                 g_free(tmp);
890                 return -1;
891         }
892
893         news_post(folder, tmp);
894         remove(tmp);
895
896         g_free(tmp);
897
898         return 0;
899 }
900
901 static gchar *news_folder_get_path(Folder *folder)
902 {
903         gchar *folder_path;
904
905         cm_return_val_if_fail(folder->account != NULL, NULL);
906
907         folder_path = g_strconcat(get_news_cache_dir(),
908                                   G_DIR_SEPARATOR_S,
909                                   folder->account->nntp_server,
910                                   NULL);
911         return folder_path;
912 }
913
914 static gchar *news_item_get_path(Folder *folder, FolderItem *item)
915 {
916         gchar *folder_path, *path;
917
918         cm_return_val_if_fail(folder != NULL, NULL);
919         cm_return_val_if_fail(item != NULL, NULL);
920         folder_path = news_folder_get_path(folder);
921
922         cm_return_val_if_fail(folder_path != NULL, NULL);
923         if (g_path_is_absolute(folder_path)) {
924                 if (item->path)
925                         path = g_strconcat(folder_path, G_DIR_SEPARATOR_S,
926                                            item->path, NULL);
927                 else
928                         path = g_strdup(folder_path);
929         } else {
930                 if (item->path)
931                         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
932                                            folder_path, G_DIR_SEPARATOR_S,
933                                            item->path, NULL);
934                 else
935                         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
936                                            folder_path, NULL);
937         }
938         g_free(folder_path);
939 #ifdef G_OS_WIN32
940         while (strchr(path, '/'))
941                 *strchr(path, '/') = '\\';
942 #endif
943         return path;
944 }
945
946 static gint news_get_num_list(Folder *folder, FolderItem *item, GSList **msgnum_list, gboolean *old_uids_valid)
947 {
948         NewsSession *session;
949         gint i, ok, num, first, last, nummsgs = 0;
950         gchar *dir;
951
952         cm_return_val_if_fail(item != NULL, -1);
953         cm_return_val_if_fail(item->folder != NULL, -1);
954         cm_return_val_if_fail(FOLDER_CLASS(folder) == &news_class, -1);
955
956         session = news_session_get(folder);
957         cm_return_val_if_fail(session != NULL, -1);
958
959         *old_uids_valid = TRUE;
960         
961         news_folder_lock(NEWS_FOLDER(item->folder));
962
963         ok = news_select_group(folder, item->path, &num, &first, &last);
964         if (ok != NEWSNNTP_NO_ERROR) {
965                 log_warning(LOG_PROTOCOL, _("couldn't set group: %s\n"), item->path);
966                 news_folder_unlock(NEWS_FOLDER(item->folder));
967                 return -1;
968         }
969
970         dir = news_folder_get_path(folder);
971         if (num <= 0)
972                 remove_all_numbered_files(dir);
973         else if (last < first)
974                 log_warning(LOG_PROTOCOL, _("invalid article range: %d - %d\n"),
975                             first, last);
976         else {
977                 for (i = first; i <= last; i++) {
978                         *msgnum_list = g_slist_prepend(*msgnum_list, 
979                                                        GINT_TO_POINTER(i));
980                         nummsgs++;
981                 }
982                 debug_print("removing old messages from %d to %d in %s\n",
983                             first, last, dir);
984                 remove_numbered_files(dir, 1, first - 1);
985         }
986         g_free(dir);
987         news_folder_unlock(NEWS_FOLDER(item->folder));
988         return nummsgs;
989 }
990
991 static void news_set_msg_flags(FolderItem *item, MsgInfo *msginfo)
992 {
993         msginfo->flags.tmp_flags = 0;
994         if (item->folder->account->mark_crosspost_read && msginfo->msgid) {
995                 if (item->folder->newsart &&
996                     g_hash_table_lookup(item->folder->newsart, msginfo->msgid) != NULL) {
997                         msginfo->flags.perm_flags = MSG_COLORLABEL_TO_FLAGS(item->folder->account->crosspost_col);
998                                 
999                 } else {
1000                         if (!item->folder->newsart) 
1001                                 item->folder->newsart = g_hash_table_new(g_str_hash, g_str_equal);
1002                         g_hash_table_insert(item->folder->newsart,
1003                                         g_strdup(msginfo->msgid), GINT_TO_POINTER(1));
1004                         msginfo->flags.perm_flags = MSG_NEW|MSG_UNREAD;
1005                 }
1006         } else {
1007                 msginfo->flags.perm_flags = MSG_NEW|MSG_UNREAD;
1008         }
1009 }
1010
1011 static void news_get_extra_fields(NewsSession *session, FolderItem *item, GSList *msglist)
1012 {
1013         MsgInfo *msginfo = NULL;
1014         gint ok;
1015         GSList *cur;
1016         clist *hdrlist = NULL;
1017         clistiter *hdr;
1018         gint first = -1, last = -1;
1019         GHashTable *hash_table;
1020         
1021         cm_return_if_fail(session != NULL);
1022         cm_return_if_fail(item != NULL);
1023         cm_return_if_fail(item->folder != NULL);
1024         cm_return_if_fail(FOLDER_CLASS(item->folder) == &news_class);
1025
1026         news_folder_lock(NEWS_FOLDER(item->folder));
1027
1028         hash_table = g_hash_table_new(g_direct_hash, g_direct_equal);
1029         
1030         for (cur = msglist; cur; cur = cur->next) {
1031                 msginfo = (MsgInfo *)cur->data;
1032                 if (first == -1 || msginfo->msgnum < first)
1033                         first = msginfo->msgnum;
1034                 if (last == -1 || msginfo->msgnum > last)
1035                         last = msginfo->msgnum;
1036                 g_hash_table_insert(hash_table,
1037                                 GINT_TO_POINTER(msginfo->msgnum), msginfo);
1038         }
1039
1040 /* Newsgroups */
1041         ok = nntp_threaded_xhdr(item->folder, "newsgroups", first, last, &hdrlist);
1042
1043         if (ok != NEWSNNTP_NO_ERROR) {
1044                 log_warning(LOG_PROTOCOL, _("couldn't get xhdr\n"));
1045                 if (ok == NEWSNNTP_ERROR_STREAM) {
1046                         session_destroy(SESSION(session));
1047                         REMOTE_FOLDER(item->folder)->session = NULL;
1048                 }
1049                 news_folder_unlock(NEWS_FOLDER(item->folder));
1050                 return;
1051         }
1052
1053         for (hdr = clist_begin(hdrlist); hdr; hdr = clist_next(hdr)) {
1054                 struct newsnntp_xhdr_resp_item *hdrval = clist_content(hdr);
1055                 msginfo = g_hash_table_lookup(hash_table, GINT_TO_POINTER(hdrval->hdr_article));
1056                 if (msginfo) {
1057                         if (msginfo->newsgroups)
1058                                 g_free(msginfo->newsgroups);
1059                         msginfo->newsgroups = g_strdup(hdrval->hdr_value);
1060                 }
1061         }
1062         newsnntp_xhdr_free(hdrlist);
1063         
1064 /* To */
1065         ok = nntp_threaded_xhdr(item->folder, "to", first, last, &hdrlist);
1066
1067         if (ok != NEWSNNTP_NO_ERROR) {
1068                 log_warning(LOG_PROTOCOL, _("couldn't get xhdr\n"));
1069                 if (ok == NEWSNNTP_ERROR_STREAM) {
1070                         session_destroy(SESSION(session));
1071                         REMOTE_FOLDER(item->folder)->session = NULL;
1072                 }
1073                 news_folder_unlock(NEWS_FOLDER(item->folder));
1074                 return;
1075         }
1076
1077         for (hdr = clist_begin(hdrlist); hdr; hdr = clist_next(hdr)) {
1078                 struct newsnntp_xhdr_resp_item *hdrval = clist_content(hdr);
1079                 msginfo = g_hash_table_lookup(hash_table, GINT_TO_POINTER(hdrval->hdr_article));
1080                 if (msginfo) {
1081                         if (msginfo->to)
1082                                 g_free(msginfo->to);
1083                         msginfo->to = g_strdup(hdrval->hdr_value);
1084                 }
1085         }
1086         newsnntp_xhdr_free(hdrlist);
1087         
1088 /* Cc */
1089         ok = nntp_threaded_xhdr(item->folder, "cc", first, last, &hdrlist);
1090
1091         if (ok != NEWSNNTP_NO_ERROR) {
1092                 log_warning(LOG_PROTOCOL, _("couldn't get xhdr\n"));
1093                 if (ok == NEWSNNTP_ERROR_STREAM) {
1094                         session_destroy(SESSION(session));
1095                         REMOTE_FOLDER(item->folder)->session = NULL;
1096                 }
1097                 news_folder_unlock(NEWS_FOLDER(item->folder));
1098                 return;
1099         }
1100
1101         for (hdr = clist_begin(hdrlist); hdr; hdr = clist_next(hdr)) {
1102                 struct newsnntp_xhdr_resp_item *hdrval = clist_content(hdr);
1103                 msginfo = g_hash_table_lookup(hash_table, GINT_TO_POINTER(hdrval->hdr_article));
1104                 if (msginfo) {
1105                         if (msginfo->cc)
1106                                 g_free(msginfo->cc);
1107                         msginfo->cc = g_strdup(hdrval->hdr_value);
1108                 }
1109         }
1110         newsnntp_xhdr_free(hdrlist);
1111         
1112
1113         g_hash_table_destroy(hash_table);
1114         news_folder_unlock(NEWS_FOLDER(item->folder));
1115 }
1116
1117 static GSList *news_get_msginfos_for_range(NewsSession *session, FolderItem *item, guint begin, guint end)
1118 {
1119         GSList *newlist = NULL;
1120         GSList *llast = NULL;
1121         MsgInfo *msginfo;
1122         gint ok;
1123         clist *msglist = NULL;
1124         clistiter *cur;
1125         cm_return_val_if_fail(session != NULL, NULL);
1126         cm_return_val_if_fail(item != NULL, NULL);
1127
1128         log_message(LOG_PROTOCOL, _("getting xover %d - %d in %s...\n"),
1129                     begin, end, item->path);
1130
1131         news_folder_lock(NEWS_FOLDER(item->folder));
1132         
1133         ok = nntp_threaded_xover(item->folder, begin, end, NULL, &msglist);
1134         
1135         if (ok != NEWSNNTP_NO_ERROR) {
1136                 log_warning(LOG_PROTOCOL, _("couldn't get xover\n"));
1137                 if (ok == NEWSNNTP_ERROR_STREAM) {
1138                         session_destroy(SESSION(session));
1139                         REMOTE_FOLDER(item->folder)->session = NULL;
1140                 }
1141                 news_folder_unlock(NEWS_FOLDER(item->folder));
1142                 return NULL;
1143         }
1144
1145         if (msglist) {
1146                 for (cur = clist_begin(msglist); cur; cur = clist_next(cur)) {
1147                         struct newsnntp_xover_resp_item *ritem = (struct newsnntp_xover_resp_item *)clist_content(cur);
1148                         msginfo = news_parse_xover(ritem);
1149                         
1150                         if (!msginfo) {
1151                                 log_warning(LOG_PROTOCOL, _("invalid xover line\n"));
1152                                 continue;
1153                         }
1154
1155                         msginfo->folder = item;
1156                         news_set_msg_flags(item, msginfo);
1157                         msginfo->flags.tmp_flags |= MSG_NEWS;
1158
1159                         if (!newlist)
1160                                 llast = newlist = g_slist_append(newlist, msginfo);
1161                         else {
1162                                 llast = g_slist_append(llast, msginfo);
1163                                 llast = llast->next;
1164                         }
1165                 }
1166                 newsnntp_xover_resp_list_free(msglist);
1167         }
1168
1169         news_folder_unlock(NEWS_FOLDER(item->folder));
1170
1171         session_set_access_time(SESSION(session));
1172
1173         news_get_extra_fields(session, item, newlist);
1174         
1175         return newlist;
1176 }
1177
1178 static MsgInfo *news_get_msginfo(Folder *folder, FolderItem *item, gint num)
1179 {
1180         GSList *msglist = NULL;
1181         NewsSession *session;
1182         MsgInfo *msginfo = NULL;
1183
1184         session = news_session_get(folder);
1185         cm_return_val_if_fail(session != NULL, NULL);
1186         cm_return_val_if_fail(item != NULL, NULL);
1187         cm_return_val_if_fail(item->folder != NULL, NULL);
1188         cm_return_val_if_fail(FOLDER_CLASS(item->folder) == &news_class, NULL);
1189
1190         msglist = news_get_msginfos_for_range(session, item, num, num);
1191  
1192         if (msglist)
1193                 msginfo = msglist->data;
1194         
1195         g_slist_free(msglist);
1196         
1197         return msginfo;
1198 }
1199
1200 static GSList *news_get_msginfos(Folder *folder, FolderItem *item, GSList *msgnum_list)
1201 {
1202         NewsSession *session;
1203         GSList *elem, *msginfo_list = NULL, *tmp_msgnum_list, *tmp_msginfo_list;
1204         guint first, last, next;
1205         guint tofetch, fetched;
1206         
1207         cm_return_val_if_fail(folder != NULL, NULL);
1208         cm_return_val_if_fail(FOLDER_CLASS(folder) == &news_class, NULL);
1209         cm_return_val_if_fail(msgnum_list != NULL, NULL);
1210         cm_return_val_if_fail(item != NULL, NULL);
1211         
1212         session = news_session_get(folder);
1213         cm_return_val_if_fail(session != NULL, NULL);
1214
1215         tmp_msgnum_list = g_slist_copy(msgnum_list);
1216         tmp_msgnum_list = g_slist_sort(tmp_msgnum_list, g_int_compare);
1217
1218         progressindicator_start(PROGRESS_TYPE_NETWORK);
1219         tofetch = g_slist_length(tmp_msgnum_list);
1220         fetched = 0;
1221
1222         first = GPOINTER_TO_INT(tmp_msgnum_list->data);
1223         last = first;
1224         
1225         news_folder_lock(NEWS_FOLDER(item->folder));
1226         
1227         for(elem = g_slist_next(tmp_msgnum_list); elem != NULL; elem = g_slist_next(elem)) {
1228                 next = GPOINTER_TO_INT(elem->data);
1229                 if(next != (last + 1)) {
1230 /*                      session->fetch_base_percentage = ((gfloat) fetched) / ((gfloat) tofetch);
1231                         session->fetch_total_percentage = ((gfloat) (last - first + 1)) / ((gfloat) tofetch);
1232 */
1233                         tmp_msginfo_list = news_get_msginfos_for_range(session, item, first, last);
1234                         msginfo_list = g_slist_concat(msginfo_list, tmp_msginfo_list);
1235                         fetched = last - first + 1;
1236                         first = next;
1237                 }
1238                 last = next;
1239         }
1240         
1241         news_folder_unlock(NEWS_FOLDER(item->folder));
1242         
1243 /*      session->fetch_base_percentage = ((gfloat) fetched) / ((gfloat) tofetch);
1244         session->fetch_total_percentage = ((gfloat) (last - first + 1)) / ((gfloat) tofetch);
1245 */
1246         tmp_msginfo_list = news_get_msginfos_for_range(session, item, first, last);
1247         msginfo_list = g_slist_concat(msginfo_list, tmp_msginfo_list);
1248
1249         g_slist_free(tmp_msgnum_list);
1250         
1251         progressindicator_stop(PROGRESS_TYPE_NETWORK);
1252
1253         return msginfo_list;
1254 }
1255
1256 static gboolean news_scan_required(Folder *folder, FolderItem *item)
1257 {
1258         return TRUE;
1259 }
1260
1261 void news_synchronise(FolderItem *item, gint days) 
1262 {
1263         news_gtk_synchronise(item, days);
1264 }
1265
1266 static gint news_rename_folder(Folder *folder, FolderItem *item,
1267                                 const gchar *name)
1268 {
1269         gchar *path;
1270          
1271         cm_return_val_if_fail(folder != NULL, -1);
1272         cm_return_val_if_fail(item != NULL, -1);
1273         cm_return_val_if_fail(item->path != NULL, -1);
1274         cm_return_val_if_fail(name != NULL, -1);
1275
1276         path = folder_item_get_path(item);
1277         if (!is_dir_exist(path))
1278                 make_dir_hier(path);
1279
1280         g_free(item->name);
1281         item->name = g_strdup(name);
1282
1283         return 0;
1284 }
1285
1286 static gint news_remove_folder(Folder *folder, FolderItem *item)
1287 {
1288         gchar *path;
1289
1290         cm_return_val_if_fail(folder != NULL, -1);
1291         cm_return_val_if_fail(item != NULL, -1);
1292         cm_return_val_if_fail(item->path != NULL, -1);
1293
1294         path = folder_item_get_path(item);
1295         if (remove_dir_recursive(path) < 0) {
1296                 g_warning("can't remove directory `%s'\n", path);
1297                 g_free(path);
1298                 return -1;
1299         }
1300
1301         g_free(path);
1302         folder_item_remove(item);
1303         return 0;
1304 }
1305
1306 #else
1307 #include <glib.h>
1308 #include <glib/gi18n.h>
1309 #include <gtk/gtk.h>
1310 #include "folder.h"
1311 #include "alertpanel.h"
1312
1313 static FolderClass news_class;
1314
1315 static void warn_etpan(void)
1316 {
1317         static gboolean missing_news_warning = TRUE;
1318         if (missing_news_warning) {
1319                 missing_news_warning = FALSE;
1320                 alertpanel_error(
1321                         _("You have one or more News accounts "
1322                           "defined. However this version of "
1323                           "Claws Mail has been built without "
1324                           "News support; your News account(s) are "
1325                           "disabled.\n\n"
1326                           "You probably need to "
1327                           "install libetpan and recompile "
1328                           "Claws Mail."));
1329         }
1330 }
1331 static Folder *news_folder_new(const gchar *name, const gchar *path)
1332 {
1333         warn_etpan();
1334         return NULL;
1335 }
1336 void news_group_list_free(GSList *group_list)
1337 {
1338         warn_etpan();
1339 }
1340 void news_remove_group_list_cache(Folder *folder)
1341 {
1342         warn_etpan();
1343 }
1344 int news_folder_locked(Folder *folder)
1345 {
1346         warn_etpan();
1347         return 0;
1348 }
1349 gint news_post(Folder *folder, const gchar *file)
1350 {
1351         warn_etpan();
1352         return -1;
1353 }
1354
1355 gint news_cancel_article(Folder * folder, MsgInfo * msginfo)
1356 {
1357         warn_etpan();
1358         return -1;
1359 }
1360
1361 GSList *news_get_group_list(Folder *folder)
1362 {
1363         warn_etpan();
1364         return NULL;
1365 }
1366
1367
1368 FolderClass *news_get_class(void)
1369 {
1370         if (news_class.idstr == NULL) {
1371                 news_class.type = F_NEWS;
1372                 news_class.idstr = "news";
1373                 news_class.uistr = "News";
1374
1375                 /* Folder functions */
1376                 news_class.new_folder = news_folder_new;
1377         };
1378
1379         return &news_class;
1380 }
1381
1382
1383 #endif