better fix for bug 4819
[claws.git] / src / news.c
blob3373623fab6ccc1b340b6cc9a266fbd161f10c74
1 /*
2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2024 the Claws Mail team and Hiroyuki Yamamoto
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.
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.
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/>.
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #include "claws-features.h"
22 #endif
24 #ifdef HAVE_LIBETPAN
26 #include "defs.h"
28 #include <glib.h>
29 #include <glib/gi18n.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include <stdlib.h>
33 #include <unistd.h>
34 #include <time.h>
35 #include <libetpan/libetpan.h>
37 #include "nntp-thread.h"
38 #include "news.h"
39 #include "news_gtk.h"
40 #include "socket.h"
41 #include "recv.h"
42 #include "procmsg.h"
43 #include "procheader.h"
44 #include "folder.h"
45 #include "session.h"
46 #include "statusbar.h"
47 #include "codeconv.h"
48 #include "utils.h"
49 #include "passwordstore.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 #include "account.h"
59 #ifdef USE_GNUTLS
60 # include "ssl.h"
61 #endif
62 #include "main.h"
63 #include "file-utils.h"
65 #define NNTP_PORT 119
66 #ifdef USE_GNUTLS
67 #define NNTPS_PORT 563
68 #endif
70 typedef struct _NewsFolder NewsFolder;
71 typedef struct _NewsSession NewsSession;
73 #define NEWS_FOLDER(obj) ((NewsFolder *)obj)
74 #define NEWS_SESSION(obj) ((NewsSession *)obj)
76 struct _NewsFolder
78 RemoteFolder rfolder;
80 gboolean use_auth;
81 gboolean lock_count;
82 guint refcnt;
85 struct _NewsSession
87 Session session;
88 Folder *folder;
89 gchar *group;
92 static void news_folder_init(Folder *folder, const gchar *name,
93 const gchar *path);
95 static Folder *news_folder_new (const gchar *name,
96 const gchar *folder);
97 static void news_folder_destroy (Folder *folder);
99 static gchar *news_fetch_msg (Folder *folder,
100 FolderItem *item,
101 gint num);
102 static void news_remove_cached_msg (Folder *folder,
103 FolderItem *item,
104 MsgInfo *msginfo);
105 #ifdef USE_GNUTLS
106 static Session *news_session_new (Folder *folder,
107 const PrefsAccount *account,
108 gushort port,
109 SSLType ssl_type);
110 #else
111 static Session *news_session_new (Folder *folder,
112 const PrefsAccount *account,
113 gushort port);
114 #endif
116 static gint news_get_article (Folder *folder,
117 gint num,
118 gchar *filename);
120 static gint news_select_group (Folder *folder,
121 const gchar *group,
122 gint *num,
123 gint *first,
124 gint *last);
125 static MsgInfo *news_parse_xover (struct newsnntp_xover_resp_item *item);
126 static gint news_get_num_list (Folder *folder,
127 FolderItem *item,
128 GSList **list,
129 gboolean *old_uids_valid);
130 static MsgInfo *news_get_msginfo (Folder *folder,
131 FolderItem *item,
132 gint num);
133 static GSList *news_get_msginfos (Folder *folder,
134 FolderItem *item,
135 GSList *msgnum_list);
136 static gboolean news_scan_required (Folder *folder,
137 FolderItem *item);
139 static gchar *news_folder_get_path (Folder *folder);
140 static gchar *news_item_get_path (Folder *folder,
141 FolderItem *item);
142 static void news_synchronise (FolderItem *item, gint days);
143 static int news_remove_msg (Folder *folder,
144 FolderItem *item,
145 gint msgnum);
146 static gint news_rename_folder (Folder *folder,
147 FolderItem *item,
148 const gchar *name);
149 static gint news_remove_folder (Folder *folder,
150 FolderItem *item);
151 static FolderClass news_class;
153 FolderClass *news_get_class(void)
155 if (news_class.idstr == NULL) {
156 news_class.type = F_NEWS;
157 news_class.idstr = "news";
158 news_class.uistr = "News";
159 news_class.supports_server_search = FALSE;
161 /* Folder functions */
162 news_class.new_folder = news_folder_new;
163 news_class.destroy_folder = news_folder_destroy;
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;
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.search_msgs = folder_item_search_msgs_local;
178 news_class.remove_msg = news_remove_msg;
179 news_class.remove_cached_msg = news_remove_cached_msg;
182 return &news_class;
185 guint nntp_folder_get_refcnt(Folder *folder)
187 return ((NewsFolder *)folder)->refcnt;
190 void nntp_folder_ref(Folder *folder)
192 ((NewsFolder *)folder)->refcnt++;
195 void nntp_folder_unref(Folder *folder)
197 if (((NewsFolder *)folder)->refcnt > 0)
198 ((NewsFolder *)folder)->refcnt--;
201 static int news_remove_msg (Folder *folder,
202 FolderItem *item,
203 gint msgnum)
205 gchar *path, *filename;
207 cm_return_val_if_fail(folder != NULL, -1);
208 cm_return_val_if_fail(item != NULL, -1);
210 path = folder_item_get_path(item);
211 if (!is_dir_exist(path))
212 make_dir_hier(path);
214 filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(msgnum), NULL);
215 g_free(path);
216 claws_unlink(filename);
217 g_free(filename);
218 return 0;
221 static void news_folder_lock(NewsFolder *folder)
223 folder->lock_count++;
226 static void news_folder_unlock(NewsFolder *folder)
228 if (folder->lock_count > 0)
229 folder->lock_count--;
232 int news_folder_locked(Folder *folder)
234 if (folder == NULL)
235 return 0;
237 return NEWS_FOLDER(folder)->lock_count;
240 static Folder *news_folder_new(const gchar *name, const gchar *path)
242 Folder *folder;
244 folder = (Folder *)g_new0(NewsFolder, 1);
245 folder->klass = &news_class;
246 news_folder_init(folder, name, path);
248 return folder;
251 static void news_folder_destroy(Folder *folder)
253 gchar *dir;
255 while (nntp_folder_get_refcnt(folder) > 0)
256 gtk_main_iteration();
258 dir = news_folder_get_path(folder);
259 if (is_dir_exist(dir))
260 remove_dir_recursive(dir);
261 g_free(dir);
263 nntp_done(folder);
264 folder_remote_folder_destroy(REMOTE_FOLDER(folder));
267 static void news_folder_init(Folder *folder, const gchar *name,
268 const gchar *path)
270 folder_remote_folder_init(folder, name, path);
273 static void news_session_destroy(Session *session)
275 NewsSession *news_session = NEWS_SESSION(session);
277 cm_return_if_fail(session != NULL);
279 if (news_session->group)
280 g_free(news_session->group);
283 static gboolean nntp_ping(gpointer data)
285 Session *session = (Session *)data;
286 NewsSession *news_session = NEWS_SESSION(session);
287 int r;
288 struct tm lt;
290 if (session->state != SESSION_READY || news_folder_locked(news_session->folder))
291 return FALSE;
293 news_folder_lock(NEWS_FOLDER(news_session->folder));
295 if ((r = nntp_threaded_date(news_session->folder, &lt)) != NEWSNNTP_NO_ERROR) {
296 if (r != NEWSNNTP_ERROR_COMMAND_NOT_SUPPORTED &&
297 r != NEWSNNTP_ERROR_COMMAND_NOT_UNDERSTOOD) {
298 log_warning(LOG_PROTOCOL, _("NNTP connection to %s:%d has been"
299 " disconnected.\n"),
300 news_session->folder->account->nntp_server,
301 news_session->folder->account->set_nntpport ?
302 news_session->folder->account->nntpport : NNTP_PORT);
303 REMOTE_FOLDER(news_session->folder)->session = NULL;
304 news_folder_unlock(NEWS_FOLDER(news_session->folder));
305 session->state = SESSION_DISCONNECTED;
306 session->sock = NULL;
307 session_destroy(session);
308 return FALSE;
312 news_folder_unlock(NEWS_FOLDER(news_session->folder));
313 session_set_access_time(session);
314 return TRUE;
318 #ifdef USE_GNUTLS
319 static Session *news_session_new(Folder *folder, const PrefsAccount *account, gushort port,
320 SSLType ssl_type)
321 #else
322 static Session *news_session_new(Folder *folder, const PrefsAccount *account, gushort port)
323 #endif
325 NewsSession *session;
326 const char *server = account->nntp_server;
327 int r = 0;
328 ProxyInfo *proxy_info = NULL;
330 cm_return_val_if_fail(server != NULL, NULL);
332 log_message(LOG_PROTOCOL,
333 _("Account '%s': Connecting to NNTP server: %s:%d...\n"),
334 folder->account->account_name, server, port);
336 session = g_new0(NewsSession, 1);
337 session_init(SESSION(session), folder->account, FALSE);
338 SESSION(session)->type = SESSION_NEWS;
339 SESSION(session)->server = g_strdup(server);
340 SESSION(session)->port = port;
341 SESSION(session)->sock = NULL;
342 SESSION(session)->destroy = news_session_destroy;
344 if (account->use_proxy) {
345 if (account->use_default_proxy) {
346 proxy_info = (ProxyInfo *)&(prefs_common.proxy_info);
347 if (proxy_info->use_proxy_auth)
348 proxy_info->proxy_pass = passwd_store_get(PWS_CORE, PWS_CORE_PROXY,
349 PWS_CORE_PROXY_PASS);
350 } else {
351 proxy_info = (ProxyInfo *)&(account->proxy_info);
352 if (proxy_info->use_proxy_auth)
353 proxy_info->proxy_pass = passwd_store_get_account(account->account_id,
354 PWS_ACCOUNT_PROXY_PASS);
357 SESSION(session)->proxy_info = proxy_info;
359 nntp_init(folder);
361 #ifdef USE_GNUTLS
362 SESSION(session)->use_tls_sni = account->use_tls_sni;
363 if (ssl_type != SSL_NONE)
364 r = nntp_threaded_connect_ssl(folder, server, port, proxy_info);
365 else
366 #endif
367 r = nntp_threaded_connect(folder, server, port, proxy_info);
369 if (r != NEWSNNTP_NO_ERROR) {
370 log_error(LOG_PROTOCOL, _("Error logging in to %s:%d...\n"), server, port);
371 session_destroy(SESSION(session));
372 return NULL;
375 session->folder = folder;
377 return SESSION(session);
380 static Session *news_session_new_for_folder(Folder *folder)
382 Session *session;
383 PrefsAccount *ac;
384 const gchar *userid = NULL;
385 gchar *passwd = NULL;
386 gushort port;
387 int r;
389 cm_return_val_if_fail(folder != NULL, NULL);
390 cm_return_val_if_fail(folder->account != NULL, NULL);
392 ac = folder->account;
394 #ifdef USE_GNUTLS
395 port = ac->set_nntpport ? ac->nntpport
396 : ac->ssl_nntp ? NNTPS_PORT : NNTP_PORT;
397 session = news_session_new(folder, ac, port, ac->ssl_nntp);
398 #else
399 if (ac->ssl_nntp != SSL_NONE) {
400 if (alertpanel_full(_("Insecure connection"),
401 _("This connection is configured to be secured "
402 "using TLS, but TLS is not available "
403 "in this build of Claws Mail. \n\n"
404 "Do you want to continue connecting to this "
405 "server? The communication would not be "
406 "secure."),
407 NULL, _("_Cancel"), NULL, _("Con_tinue connecting"),
408 NULL, NULL, ALERTFOCUS_FIRST, FALSE, NULL, ALERT_WARNING) != G_ALERTALTERNATE)
409 return NULL;
411 port = ac->set_nntpport ? ac->nntpport : NNTP_PORT;
412 session = news_session_new(folder, ac, port);
413 #endif
415 if (ac->use_nntp_auth && ac->userid && ac->userid[0]) {
416 userid = ac->userid;
417 if (password_get(userid, ac->nntp_server, "nntp", port, &passwd)) {
418 /* NOP */;
419 } else if ((passwd = passwd_store_get_account(ac->account_id,
420 PWS_ACCOUNT_RECV)) == NULL) {
421 passwd = input_dialog_query_password_keep(ac->nntp_server,
422 userid,
423 &(ac->session_passwd));
427 if (session != NULL)
428 r = nntp_threaded_mode_reader(folder);
429 else
430 r = NEWSNNTP_ERROR_CONNECTION_REFUSED;
432 if (r != NEWSNNTP_NO_ERROR) {
433 if (r == NEWSNNTP_WARNING_REQUEST_AUTHORIZATION_USERNAME) {
435 FIX ME when libetpan implements 480 to indicate authorization
436 is required to use this capability. Libetpan treats a 480 as a
437 381 which is clearly wrong.
438 RFC 4643 section 2.
439 Response code 480
440 Generic response
441 Meaning: command unavailable until the client
442 has authenticated itself.
444 /* if the server does not advertise the capability MODE-READER,
445 we normally should not send MODE READER. However this can't
446 hurt: a transit-only server returns 502 and closes the cnx.
447 Ref.: http://tools.ietf.org/html/rfc3977#section-5.3
449 log_error(LOG_PROTOCOL, _("Libetpan does not support return code 480 "
450 "so for now we choose to continue\n"));
452 else if (r == NEWSNNTP_ERROR_UNEXPECTED_RESPONSE) {
453 /* if the server does not advertise the capability MODE-READER,
454 we normally should not send MODE READER. However this can't
455 hurt: a transit-only server returns 502 and closes the cnx.
456 Ref.: http://tools.ietf.org/html/rfc3977#section-5.3
458 log_error(LOG_PROTOCOL, _("Mode reader failed, continuing nevertheless\n"));
460 else {
461 /* An error state bail out */
462 log_error(LOG_PROTOCOL, _("Error creating session with %s:%d\n"), ac->nntp_server, port);
463 if (session != NULL)
464 session_destroy(SESSION(session));
465 g_free(passwd);
466 if (ac->session_passwd) {
467 g_free(ac->session_passwd);
468 ac->session_passwd = NULL;
470 return NULL;
474 if ((session != NULL) && ac->use_nntp_auth) { /* FIXME: && ac->use_nntp_auth_onconnect */
475 if (nntp_threaded_login(folder, userid, passwd) !=
476 NEWSNNTP_NO_ERROR) {
477 log_error(LOG_PROTOCOL, _("Error authenticating to %s:%d...\n"), ac->nntp_server, port);
478 session_destroy(SESSION(session));
479 g_free(passwd);
480 if (ac->session_passwd) {
481 g_free(ac->session_passwd);
482 ac->session_passwd = NULL;
484 return NULL;
487 g_free(passwd);
489 return session;
492 static NewsSession *news_session_get(Folder *folder)
494 RemoteFolder *rfolder = REMOTE_FOLDER(folder);
496 cm_return_val_if_fail(folder != NULL, NULL);
497 cm_return_val_if_fail(FOLDER_CLASS(folder) == &news_class, NULL);
498 cm_return_val_if_fail(folder->account != NULL, NULL);
500 if (prefs_common.work_offline &&
501 !inc_offline_should_override(FALSE,
502 _("Claws Mail needs network access in order "
503 "to access the News server."))) {
504 return NULL;
507 if (!rfolder->session) {
508 rfolder->session = news_session_new_for_folder(folder);
509 session_register_ping(SESSION(rfolder->session), nntp_ping);
510 return NEWS_SESSION(rfolder->session);
513 /* Handle port change (also ssl/nossl change) without needing to
514 * restart application. */
515 if (rfolder->session->port != folder->account->nntpport) {
516 session_destroy(rfolder->session);
517 rfolder->session = news_session_new_for_folder(folder);
518 session_register_ping(SESSION(rfolder->session), nntp_ping);
519 goto newsession;
522 if (time(NULL) - rfolder->session->last_access_time <
523 SESSION_TIMEOUT_INTERVAL) {
524 return NEWS_SESSION(rfolder->session);
527 if (!nntp_ping(rfolder->session)) {
528 rfolder->session = news_session_new_for_folder(folder);
529 session_register_ping(SESSION(rfolder->session), nntp_ping);
532 newsession:
533 if (rfolder->session)
534 session_set_access_time(rfolder->session);
536 return NEWS_SESSION(rfolder->session);
539 static void news_remove_cached_msg(Folder *folder, FolderItem *item, MsgInfo *msginfo)
541 gchar *path, *filename;
543 path = folder_item_get_path(item);
545 if (!is_dir_exist(path)) {
546 g_free(path);
547 return;
550 filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(msginfo->msgnum), NULL);
551 g_free(path);
553 if (is_file_exist(filename)) {
554 claws_unlink(filename);
556 g_free(filename);
559 static gchar *news_fetch_msg(Folder *folder, FolderItem *item, gint num)
561 gchar *path, *filename;
562 NewsSession *session;
563 gint ok;
565 cm_return_val_if_fail(folder != NULL, NULL);
566 cm_return_val_if_fail(item != NULL, NULL);
568 path = folder_item_get_path(item);
569 if (!is_dir_exist(path))
570 make_dir_hier(path);
571 filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(num), NULL);
572 g_free(path);
574 if (is_file_exist(filename)) {
575 debug_print("article %d has been already cached.\n", num);
576 return filename;
579 session = news_session_get(folder);
580 if (!session) {
581 g_free(filename);
582 return NULL;
585 ok = news_select_group(folder, item->path, NULL, NULL, NULL);
586 if (ok != NEWSNNTP_NO_ERROR) {
587 if (ok == NEWSNNTP_ERROR_STREAM) {
588 session_destroy(SESSION(session));
589 REMOTE_FOLDER(folder)->session = NULL;
591 g_free(filename);
592 return NULL;
595 debug_print("getting article %d...\n", num);
596 ok = news_get_article(folder,
597 num, filename);
598 if (ok != NEWSNNTP_NO_ERROR) {
599 g_warning("can't read article %d", num);
600 if (ok == NEWSNNTP_ERROR_STREAM) {
601 session_destroy(SESSION(session));
602 REMOTE_FOLDER(folder)->session = NULL;
604 g_free(filename);
605 return NULL;
607 GTK_EVENTS_FLUSH();
608 return filename;
611 static NewsGroupInfo *news_group_info_new(const gchar *name,
612 gint first, gint last, gchar type)
614 NewsGroupInfo *ginfo;
616 ginfo = g_new(NewsGroupInfo, 1);
617 ginfo->name = g_strdup(name);
618 ginfo->first = first;
619 ginfo->last = last;
620 ginfo->type = type;
622 return ginfo;
625 static void news_group_info_free(NewsGroupInfo *ginfo)
627 g_free(ginfo->name);
628 g_free(ginfo);
631 static gint news_group_info_compare(NewsGroupInfo *ginfo1,
632 NewsGroupInfo *ginfo2)
634 return g_ascii_strcasecmp(ginfo1->name, ginfo2->name);
637 GSList *news_get_group_list(Folder *folder)
639 gchar *path, *filename;
640 FILE *fp;
641 GSList *list = NULL;
642 GSList *last = NULL;
643 gchar buf[BUFFSIZE];
645 cm_return_val_if_fail(folder != NULL, NULL);
646 cm_return_val_if_fail(FOLDER_CLASS(folder) == &news_class, NULL);
648 path = folder_item_get_path(FOLDER_ITEM(folder->node->data));
649 if (!is_dir_exist(path))
650 make_dir_hier(path);
651 filename = g_strconcat(path, G_DIR_SEPARATOR_S, NEWSGROUP_LIST, NULL);
652 g_free(path);
654 if ((fp = claws_fopen(filename, "rb")) == NULL) {
655 NewsSession *session;
656 gint ok;
657 clist *grouplist = NULL;
658 clistiter *cur;
659 fp = claws_fopen(filename, "wb");
661 if (!fp) {
662 g_free(filename);
663 return NULL;
665 session = news_session_get(folder);
666 if (!session) {
667 claws_fclose(fp);
668 g_free(filename);
669 return NULL;
672 ok = nntp_threaded_list(folder, &grouplist);
674 if (ok != NEWSNNTP_NO_ERROR) {
675 if (ok == NEWSNNTP_ERROR_STREAM) {
676 session_destroy(SESSION(session));
677 REMOTE_FOLDER(folder)->session = NULL;
679 claws_fclose(fp);
680 g_free(filename);
681 return NULL;
684 if (grouplist) {
685 for (cur = clist_begin(grouplist); cur; cur = clist_next(cur)) {
686 struct newsnntp_group_info *info = (struct newsnntp_group_info *)
687 clist_content(cur);
688 if (fprintf(fp, "%s %d %d %c\n",
689 info->grp_name,
690 info->grp_last,
691 info->grp_first,
692 info->grp_type) < 0) {
693 log_error(LOG_PROTOCOL, ("Can't write newsgroup list\n"));
694 session_destroy(SESSION(session));
695 REMOTE_FOLDER(folder)->session = NULL;
696 claws_fclose(fp);
697 g_free(filename);
698 newsnntp_list_free(grouplist);
699 return NULL;
702 newsnntp_list_free(grouplist);
704 if (claws_safe_fclose(fp) == EOF) {
705 log_error(LOG_PROTOCOL, ("Can't write newsgroup list\n"));
706 session_destroy(SESSION(session));
707 REMOTE_FOLDER(folder)->session = NULL;
708 g_free(filename);
709 return NULL;
712 if ((fp = claws_fopen(filename, "rb")) == NULL) {
713 FILE_OP_ERROR(filename, "claws_fopen");
714 g_free(filename);
715 return NULL;
719 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
720 gchar *p = buf;
721 gchar *name;
722 gint last_num;
723 gint first_num;
724 gchar type;
725 NewsGroupInfo *ginfo;
727 p = strchr(p, ' ');
728 if (!p) continue;
729 *p = '\0';
730 p++;
731 name = buf;
733 if (sscanf(p, "%d %d %c", &last_num, &first_num, &type) < 3)
734 continue;
736 ginfo = news_group_info_new(name, first_num, last_num, type);
738 if (!last)
739 last = list = g_slist_append(NULL, ginfo);
740 else {
741 last = g_slist_append(last, ginfo);
742 last = last->next;
746 claws_fclose(fp);
747 g_free(filename);
749 list = g_slist_sort(list, (GCompareFunc)news_group_info_compare);
751 return list;
754 void news_group_list_free(GSList *group_list)
756 GSList *cur;
758 if (!group_list) return;
760 for (cur = group_list; cur != NULL; cur = cur->next)
761 news_group_info_free((NewsGroupInfo *)cur->data);
762 g_slist_free(group_list);
765 void news_remove_group_list_cache(Folder *folder)
767 gchar *path, *filename;
769 cm_return_if_fail(folder != NULL);
770 cm_return_if_fail(FOLDER_CLASS(folder) == &news_class);
772 path = folder_item_get_path(FOLDER_ITEM(folder->node->data));
773 filename = g_strconcat(path, G_DIR_SEPARATOR_S, NEWSGROUP_LIST, NULL);
774 g_free(path);
776 if (is_file_exist(filename)) {
777 if (claws_unlink(filename) < 0)
778 FILE_OP_ERROR(filename, "remove");
780 g_free(filename);
783 gint news_post(Folder *folder, const gchar *file)
785 gint ok;
786 char *contents = file_read_to_str_no_recode(file);
787 NewsSession *session;
789 cm_return_val_if_fail(folder != NULL, -1);
790 cm_return_val_if_fail(FOLDER_CLASS(folder) == &news_class, -1);
791 cm_return_val_if_fail(contents != NULL, -1);
793 session = news_session_get(folder);
794 if (!session) {
795 g_free(contents);
796 return -1;
799 ok = nntp_threaded_post(folder, contents, strlen(contents));
801 g_free(contents);
803 if (ok == NEWSNNTP_ERROR_STREAM) {
804 session_destroy(SESSION(session));
805 REMOTE_FOLDER(folder)->session = NULL;
808 return (ok == NEWSNNTP_NO_ERROR ? 0 : -1);
811 static gint news_get_article(Folder *folder, gint num, gchar *filename)
813 size_t len;
814 char *result = NULL;
815 int r;
817 r = nntp_threaded_article(folder, num, &result, &len);
819 if (r == NEWSNNTP_NO_ERROR) {
820 if (str_write_to_file(result, filename, FALSE) < 0) {
821 mmap_string_unref(result);
822 return -1;
824 mmap_string_unref(result);
827 return r;
831 * news_select_group:
832 * @session: Active NNTP session.
833 * @group: Newsgroup name.
834 * @num: Estimated number of articles.
835 * @first: First article number.
836 * @last: Last article number.
838 * Select newsgroup @group with the GROUP command if it is not already
839 * selected in @session, or article numbers need to be returned.
841 * Return value: NNTP result code.
843 static gint news_select_group(Folder *folder, const gchar *group,
844 gint *num, gint *first, gint *last)
846 gint ok;
847 gint num_, first_, last_;
848 struct newsnntp_group_info *info = NULL;
849 NewsSession *session = NEWS_SESSION(news_session_get(folder));
851 cm_return_val_if_fail(session != NULL, -1);
853 if (!num || !first || !last) {
854 if (session->group && g_ascii_strcasecmp(session->group, group) == 0)
855 return NEWSNNTP_NO_ERROR;
856 num = &num_;
857 first = &first_;
858 last = &last_;
861 g_free(session->group);
862 session->group = NULL;
864 ok = nntp_threaded_group(folder, group, &info);
866 if (ok == NEWSNNTP_NO_ERROR && info) {
867 session->group = g_strdup(group);
868 *num = info->grp_first;
869 *first = info->grp_first;
870 *last = info->grp_last;
871 newsnntp_group_free(info);
872 } else {
873 log_warning(LOG_PROTOCOL, _("couldn't select group: %s\n"), group);
875 return ok;
878 static MsgInfo *news_parse_xover(struct newsnntp_xover_resp_item *item)
880 MsgInfo *msginfo;
882 /* set MsgInfo */
883 msginfo = procmsg_msginfo_new();
884 msginfo->msgnum = item->ovr_article;
885 msginfo->size = item->ovr_size;
887 msginfo->date = g_strdup(item->ovr_date);
888 msginfo->date_t = procheader_date_parse(NULL, item->ovr_date, 0);
890 msginfo->from = conv_unmime_header(item->ovr_author, NULL, TRUE);
891 msginfo->fromname = procheader_get_fromname(msginfo->from);
893 msginfo->subject = conv_unmime_header(item->ovr_subject, NULL, TRUE);
895 remove_return(msginfo->from);
896 remove_return(msginfo->fromname);
897 remove_return(msginfo->subject);
899 if (item->ovr_message_id) {
900 gchar *tmp = g_strdup(item->ovr_message_id);
901 extract_parenthesis(tmp, '<', '>');
902 remove_space(tmp);
903 if (*tmp != '\0')
904 msginfo->msgid = g_strdup(tmp);
905 g_free(tmp);
908 /* FIXME: this is a quick fix; references' meaning was changed
909 * into having the actual list of references in the References: header.
910 * We need a GSList here, so msginfo_free() and msginfo_copy() can do
911 * their things properly. */
912 if (item->ovr_references && *(item->ovr_references)) {
913 gchar **ref_tokens = g_strsplit(item->ovr_references, " ", -1);
914 guint i = 0;
915 char *tmp;
916 char *p;
917 while (ref_tokens[i]) {
918 gchar *cur_ref = ref_tokens[i];
919 msginfo->references = references_list_append(msginfo->references,
920 cur_ref);
921 i++;
923 g_strfreev(ref_tokens);
925 tmp = g_strdup(item->ovr_references);
926 eliminate_parenthesis(tmp, '(', ')');
927 if ((p = strrchr(tmp, '<')) != NULL) {
928 extract_parenthesis(p, '<', '>');
929 remove_space(p);
930 if (*p != '\0')
931 msginfo->inreplyto = g_strdup(p);
933 g_free(tmp);
936 return msginfo;
939 gint news_cancel_article(Folder * folder, MsgInfo * msginfo)
941 gchar * tmp;
942 FILE * tmpfp;
943 gchar date[RFC822_DATE_BUFFSIZE];
945 tmp = g_strdup_printf("%s%ccancel%p", get_tmp_dir(),
946 G_DIR_SEPARATOR, msginfo);
947 if (tmp == NULL)
948 return -1;
950 if ((tmpfp = claws_fopen(tmp, "wb")) == NULL) {
951 FILE_OP_ERROR(tmp, "claws_fopen");
952 g_free(tmp);
953 return -1;
955 if (change_file_mode_rw(tmpfp, tmp) < 0) {
956 FILE_OP_ERROR(tmp, "chmod");
957 g_warning("can't change file mode");
960 if (prefs_common.hide_timezone)
961 get_rfc822_date_hide_tz(date, sizeof(date));
962 else
963 get_rfc822_date(date, sizeof(date));
964 if (fprintf(tmpfp, "From: %s\r\n"
965 "Newsgroups: %s\r\n"
966 "Subject: cmsg cancel <%s>\r\n"
967 "Control: cancel <%s>\r\n"
968 "Approved: %s\r\n"
969 "X-Cancelled-by: %s\r\n"
970 "Date: %s\r\n"
971 "\r\n"
972 "removed with Claws Mail\r\n",
973 msginfo->from,
974 msginfo->newsgroups,
975 msginfo->msgid,
976 msginfo->msgid,
977 msginfo->from,
978 msginfo->from,
979 date) < 0) {
980 FILE_OP_ERROR(tmp, "fprintf");
981 claws_fclose(tmpfp);
982 claws_unlink(tmp);
983 g_free(tmp);
984 return -1;
987 if (claws_safe_fclose(tmpfp) == EOF) {
988 FILE_OP_ERROR(tmp, "claws_fclose");
989 claws_unlink(tmp);
990 g_free(tmp);
991 return -1;
994 news_post(folder, tmp);
995 claws_unlink(tmp);
997 g_free(tmp);
999 return 0;
1002 static gchar *news_folder_get_path(Folder *folder)
1004 gchar *folder_path;
1006 cm_return_val_if_fail(folder->account != NULL, NULL);
1008 folder_path = g_strconcat(get_news_cache_dir(),
1009 G_DIR_SEPARATOR_S,
1010 folder->account->nntp_server,
1011 NULL);
1012 return folder_path;
1015 static gchar *news_item_get_path(Folder *folder, FolderItem *item)
1017 gchar *folder_path, *path;
1019 cm_return_val_if_fail(folder != NULL, NULL);
1020 cm_return_val_if_fail(item != NULL, NULL);
1021 folder_path = news_folder_get_path(folder);
1023 cm_return_val_if_fail(folder_path != NULL, NULL);
1024 if (g_path_is_absolute(folder_path)) {
1025 if (item->path)
1026 path = g_strconcat(folder_path, G_DIR_SEPARATOR_S,
1027 item->path, NULL);
1028 else
1029 path = g_strdup(folder_path);
1030 } else {
1031 if (item->path)
1032 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1033 folder_path, G_DIR_SEPARATOR_S,
1034 item->path, NULL);
1035 else
1036 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1037 folder_path, NULL);
1039 g_free(folder_path);
1040 #ifdef G_OS_WIN32
1041 while (strchr(path, '/'))
1042 *strchr(path, '/') = '\\';
1043 #endif
1044 return path;
1047 static gint news_get_num_list(Folder *folder, FolderItem *item, GSList **msgnum_list, gboolean *old_uids_valid)
1049 NewsSession *session;
1050 gint i, ok, num, first, last, nummsgs = 0;
1051 gchar *dir;
1053 cm_return_val_if_fail(item != NULL, -1);
1054 cm_return_val_if_fail(item->folder != NULL, -1);
1055 cm_return_val_if_fail(FOLDER_CLASS(folder) == &news_class, -1);
1057 session = news_session_get(folder);
1058 cm_return_val_if_fail(session != NULL, -1);
1060 *old_uids_valid = TRUE;
1062 news_folder_lock(NEWS_FOLDER(item->folder));
1064 ok = news_select_group(folder, item->path, &num, &first, &last);
1065 if (ok != NEWSNNTP_NO_ERROR) {
1066 log_warning(LOG_PROTOCOL, _("couldn't set group: %s\n"), item->path);
1067 news_folder_unlock(NEWS_FOLDER(item->folder));
1068 return -1;
1071 dir = news_folder_get_path(folder);
1072 if (num <= 0)
1073 remove_all_numbered_files(dir);
1074 else if (last < first)
1075 log_warning(LOG_PROTOCOL, _("invalid article range: %d - %d\n"),
1076 first, last);
1077 else {
1078 for (i = first; i <= last; i++) {
1079 *msgnum_list = g_slist_prepend(*msgnum_list,
1080 GINT_TO_POINTER(i));
1081 nummsgs++;
1083 debug_print("removing old messages from %d to %d in %s\n",
1084 first, last, dir);
1085 remove_numbered_files(dir, 1, first - 1);
1087 g_free(dir);
1088 news_folder_unlock(NEWS_FOLDER(item->folder));
1089 return nummsgs;
1092 static void news_set_msg_flags(FolderItem *item, MsgInfo *msginfo)
1094 msginfo->flags.tmp_flags = 0;
1095 if (item->folder->account->mark_crosspost_read && msginfo->msgid) {
1096 if (item->folder->newsart &&
1097 g_hash_table_lookup(item->folder->newsart, msginfo->msgid) != NULL) {
1098 msginfo->flags.perm_flags = MSG_COLORLABEL_TO_FLAGS(item->folder->account->crosspost_col);
1100 } else {
1101 if (!item->folder->newsart)
1102 item->folder->newsart = g_hash_table_new(g_str_hash, g_str_equal);
1103 g_hash_table_insert(item->folder->newsart,
1104 g_strdup(msginfo->msgid), GINT_TO_POINTER(1));
1105 msginfo->flags.perm_flags = MSG_NEW|MSG_UNREAD;
1107 } else {
1108 msginfo->flags.perm_flags = MSG_NEW|MSG_UNREAD;
1112 static void news_get_extra_fields(NewsSession *session, FolderItem *item, GSList *msglist)
1114 MsgInfo *msginfo = NULL;
1115 gint ok;
1116 GSList *cur;
1117 clist *hdrlist = NULL;
1118 clistiter *hdr;
1119 gint first = -1, last = -1;
1120 GHashTable *hash_table;
1122 cm_return_if_fail(session != NULL);
1123 cm_return_if_fail(item != NULL);
1124 cm_return_if_fail(item->folder != NULL);
1125 cm_return_if_fail(FOLDER_CLASS(item->folder) == &news_class);
1127 if (msglist == NULL)
1128 return;
1130 news_folder_lock(NEWS_FOLDER(item->folder));
1132 hash_table = g_hash_table_new(g_direct_hash, g_direct_equal);
1134 for (cur = msglist; cur; cur = cur->next) {
1135 msginfo = (MsgInfo *)cur->data;
1136 if (first == -1 || msginfo->msgnum < first)
1137 first = msginfo->msgnum;
1138 if (last == -1 || msginfo->msgnum > last)
1139 last = msginfo->msgnum;
1140 g_hash_table_insert(hash_table,
1141 GINT_TO_POINTER(msginfo->msgnum), msginfo);
1144 if (first == -1 || last == -1) {
1145 g_hash_table_destroy(hash_table);
1146 return;
1149 /* Newsgroups */
1150 ok = nntp_threaded_xhdr(item->folder, "newsgroups", first, last, &hdrlist);
1152 if (ok != NEWSNNTP_NO_ERROR) {
1153 log_warning(LOG_PROTOCOL, _("couldn't get xhdr\n"));
1154 if (ok == NEWSNNTP_ERROR_STREAM) {
1155 session_destroy(SESSION(session));
1156 REMOTE_FOLDER(item->folder)->session = NULL;
1158 news_folder_unlock(NEWS_FOLDER(item->folder));
1159 if (hdrlist != NULL)
1160 newsnntp_xhdr_free(hdrlist);
1161 return;
1164 for (hdr = clist_begin(hdrlist); hdr; hdr = clist_next(hdr)) {
1165 struct newsnntp_xhdr_resp_item *hdrval = clist_content(hdr);
1166 msginfo = g_hash_table_lookup(hash_table, GINT_TO_POINTER(hdrval->hdr_article));
1167 if (msginfo) {
1168 if (msginfo->newsgroups)
1169 g_free(msginfo->newsgroups);
1170 msginfo->newsgroups = g_strdup(hdrval->hdr_value);
1173 newsnntp_xhdr_free(hdrlist);
1174 hdrlist = NULL;
1176 /* To */
1177 ok = nntp_threaded_xhdr(item->folder, "to", first, last, &hdrlist);
1179 if (ok != NEWSNNTP_NO_ERROR) {
1180 log_warning(LOG_PROTOCOL, _("couldn't get xhdr\n"));
1181 if (ok == NEWSNNTP_ERROR_STREAM) {
1182 session_destroy(SESSION(session));
1183 REMOTE_FOLDER(item->folder)->session = NULL;
1185 news_folder_unlock(NEWS_FOLDER(item->folder));
1186 if (hdrlist != NULL)
1187 newsnntp_xhdr_free(hdrlist);
1188 return;
1191 for (hdr = clist_begin(hdrlist); hdr; hdr = clist_next(hdr)) {
1192 struct newsnntp_xhdr_resp_item *hdrval = clist_content(hdr);
1193 msginfo = g_hash_table_lookup(hash_table, GINT_TO_POINTER(hdrval->hdr_article));
1194 if (msginfo) {
1195 if (msginfo->to)
1196 g_free(msginfo->to);
1197 msginfo->to = g_strdup(hdrval->hdr_value);
1200 newsnntp_xhdr_free(hdrlist);
1201 hdrlist = NULL;
1203 /* Cc */
1204 ok = nntp_threaded_xhdr(item->folder, "cc", first, last, &hdrlist);
1206 if (ok != NEWSNNTP_NO_ERROR) {
1207 log_warning(LOG_PROTOCOL, _("couldn't get xhdr\n"));
1208 if (ok == NEWSNNTP_ERROR_STREAM) {
1209 session_destroy(SESSION(session));
1210 REMOTE_FOLDER(item->folder)->session = NULL;
1212 news_folder_unlock(NEWS_FOLDER(item->folder));
1213 if (hdrlist != NULL)
1214 newsnntp_xhdr_free(hdrlist);
1215 return;
1218 for (hdr = clist_begin(hdrlist); hdr; hdr = clist_next(hdr)) {
1219 struct newsnntp_xhdr_resp_item *hdrval = clist_content(hdr);
1220 msginfo = g_hash_table_lookup(hash_table, GINT_TO_POINTER(hdrval->hdr_article));
1221 if (msginfo) {
1222 if (msginfo->cc)
1223 g_free(msginfo->cc);
1224 msginfo->cc = g_strdup(hdrval->hdr_value);
1227 newsnntp_xhdr_free(hdrlist);
1228 hdrlist = NULL;
1230 g_hash_table_destroy(hash_table);
1231 news_folder_unlock(NEWS_FOLDER(item->folder));
1234 static GSList *news_get_msginfos_for_range(NewsSession *session, FolderItem *item, guint begin, guint end)
1236 GSList *newlist = NULL;
1237 GSList *llast = NULL;
1238 MsgInfo *msginfo;
1239 gint ok;
1240 clist *msglist = NULL;
1241 clistiter *cur;
1242 cm_return_val_if_fail(session != NULL, NULL);
1243 cm_return_val_if_fail(item != NULL, NULL);
1245 log_message(LOG_PROTOCOL, _("getting xover %d - %d in %s...\n"),
1246 begin, end, item->path);
1248 news_folder_lock(NEWS_FOLDER(item->folder));
1250 ok = news_select_group(item->folder, item->path, NULL, NULL, NULL);
1251 if (ok != NEWSNNTP_NO_ERROR) {
1252 log_warning(LOG_PROTOCOL, _("couldn't set group: %s\n"), item->path);
1253 news_folder_unlock(NEWS_FOLDER(item->folder));
1254 return NULL;
1257 ok = nntp_threaded_xover(item->folder, begin, end, NULL, &msglist);
1259 if (ok != NEWSNNTP_NO_ERROR) {
1260 log_warning(LOG_PROTOCOL, _("couldn't get xover\n"));
1261 if (ok == NEWSNNTP_ERROR_STREAM) {
1262 session_destroy(SESSION(session));
1263 REMOTE_FOLDER(item->folder)->session = NULL;
1265 news_folder_unlock(NEWS_FOLDER(item->folder));
1266 if (msglist != NULL)
1267 newsnntp_xover_resp_list_free(msglist);
1268 return NULL;
1271 if (msglist) {
1272 for (cur = clist_begin(msglist); cur; cur = clist_next(cur)) {
1273 struct newsnntp_xover_resp_item *ritem = (struct newsnntp_xover_resp_item *)clist_content(cur);
1274 msginfo = news_parse_xover(ritem);
1276 if (!msginfo) {
1277 log_warning(LOG_PROTOCOL, _("invalid xover line\n"));
1278 continue;
1281 msginfo->folder = item;
1282 news_set_msg_flags(item, msginfo);
1283 msginfo->flags.tmp_flags |= MSG_NEWS;
1285 if (!newlist)
1286 llast = newlist = g_slist_append(newlist, msginfo);
1287 else {
1288 llast = g_slist_append(llast, msginfo);
1289 llast = llast->next;
1292 newsnntp_xover_resp_list_free(msglist);
1295 news_folder_unlock(NEWS_FOLDER(item->folder));
1297 session_set_access_time(SESSION(session));
1299 news_get_extra_fields(session, item, newlist);
1301 return newlist;
1304 static MsgInfo *news_get_msginfo(Folder *folder, FolderItem *item, gint num)
1306 GSList *msglist = NULL;
1307 NewsSession *session;
1308 MsgInfo *msginfo = NULL;
1310 session = news_session_get(folder);
1311 cm_return_val_if_fail(session != NULL, NULL);
1312 cm_return_val_if_fail(item != NULL, NULL);
1313 cm_return_val_if_fail(item->folder != NULL, NULL);
1314 cm_return_val_if_fail(FOLDER_CLASS(item->folder) == &news_class, NULL);
1316 msglist = news_get_msginfos_for_range(session, item, num, num);
1318 if (msglist)
1319 msginfo = msglist->data;
1321 g_slist_free(msglist);
1323 return msginfo;
1326 static GSList *news_get_msginfos(Folder *folder, FolderItem *item, GSList *msgnum_list)
1328 NewsSession *session;
1329 GSList *elem, *msginfo_list = NULL, *tmp_msgnum_list, *tmp_msginfo_list;
1330 guint first, last, next;
1332 cm_return_val_if_fail(folder != NULL, NULL);
1333 cm_return_val_if_fail(FOLDER_CLASS(folder) == &news_class, NULL);
1334 cm_return_val_if_fail(msgnum_list != NULL, NULL);
1335 cm_return_val_if_fail(item != NULL, NULL);
1337 session = news_session_get(folder);
1338 cm_return_val_if_fail(session != NULL, NULL);
1340 tmp_msgnum_list = g_slist_copy(msgnum_list);
1341 tmp_msgnum_list = g_slist_sort(tmp_msgnum_list, g_int_compare);
1343 progressindicator_start(PROGRESS_TYPE_NETWORK);
1345 first = GPOINTER_TO_INT(tmp_msgnum_list->data);
1346 last = first;
1348 news_folder_lock(NEWS_FOLDER(item->folder));
1350 for(elem = g_slist_next(tmp_msgnum_list); elem != NULL; elem = g_slist_next(elem)) {
1351 next = GPOINTER_TO_INT(elem->data);
1352 if(next != (last + 1)) {
1353 tmp_msginfo_list = news_get_msginfos_for_range(session, item, first, last);
1354 msginfo_list = g_slist_concat(msginfo_list, tmp_msginfo_list);
1355 first = next;
1357 last = next;
1360 news_folder_unlock(NEWS_FOLDER(item->folder));
1362 tmp_msginfo_list = news_get_msginfos_for_range(session, item, first, last);
1363 msginfo_list = g_slist_concat(msginfo_list, tmp_msginfo_list);
1365 g_slist_free(tmp_msgnum_list);
1367 progressindicator_stop(PROGRESS_TYPE_NETWORK);
1369 return msginfo_list;
1372 static gboolean news_scan_required(Folder *folder, FolderItem *item)
1374 return TRUE;
1377 void news_synchronise(FolderItem *item, gint days)
1379 news_gtk_synchronise(item, days);
1382 static gint news_rename_folder(Folder *folder, FolderItem *item,
1383 const gchar *name)
1385 gchar *path;
1387 cm_return_val_if_fail(folder != NULL, -1);
1388 cm_return_val_if_fail(item != NULL, -1);
1389 cm_return_val_if_fail(item->path != NULL, -1);
1390 cm_return_val_if_fail(name != NULL, -1);
1392 path = folder_item_get_path(item);
1393 if (!is_dir_exist(path))
1394 make_dir_hier(path);
1396 g_free(item->name);
1397 item->name = g_strdup(name);
1399 return 0;
1402 static gint news_remove_folder(Folder *folder, FolderItem *item)
1404 gchar *path;
1406 cm_return_val_if_fail(folder != NULL, -1);
1407 cm_return_val_if_fail(item != NULL, -1);
1408 cm_return_val_if_fail(item->path != NULL, -1);
1410 path = folder_item_get_path(item);
1411 if (remove_dir_recursive(path) < 0) {
1412 g_warning("can't remove directory '%s'", path);
1413 g_free(path);
1414 return -1;
1417 g_free(path);
1418 folder_item_remove(item);
1419 return 0;
1422 void nntp_disconnect_all(gboolean have_connectivity)
1424 GList *list;
1425 gboolean short_timeout;
1426 #ifdef HAVE_NETWORKMANAGER_SUPPORT
1427 GError *error;
1428 #endif
1430 #ifdef HAVE_NETWORKMANAGER_SUPPORT
1431 error = NULL;
1432 short_timeout = !networkmanager_is_online(&error);
1433 if(error) {
1434 short_timeout = TRUE;
1435 g_error_free(error);
1437 #else
1438 short_timeout = TRUE;
1439 #endif
1441 if(short_timeout)
1442 nntp_main_set_timeout(1);
1444 for (list = account_get_list(); list != NULL; list = list->next) {
1445 PrefsAccount *account = list->data;
1446 if (account->protocol == A_NNTP) {
1447 RemoteFolder *folder = (RemoteFolder *)account->folder;
1448 if (folder && folder->session) {
1449 NewsSession *session = (NewsSession *)folder->session;
1450 if (have_connectivity)
1451 nntp_threaded_disconnect(FOLDER(folder));
1452 SESSION(session)->state = SESSION_DISCONNECTED;
1453 SESSION(session)->sock = NULL;
1454 session_destroy(SESSION(session));
1455 folder->session = NULL;
1460 if(short_timeout)
1461 nntp_main_set_timeout(prefs_common.io_timeout_secs);
1464 #else
1465 #include <glib.h>
1466 #include <glib/gi18n.h>
1467 #include <gtk/gtk.h>
1468 #include "folder.h"
1469 #include "alertpanel.h"
1471 static FolderClass news_class;
1473 static void warn_etpan(void)
1475 static gboolean missing_news_warning = TRUE;
1476 if (missing_news_warning) {
1477 missing_news_warning = FALSE;
1478 alertpanel_error(
1479 _("You have one or more News accounts "
1480 "defined. However this version of "
1481 "Claws Mail has been built without "
1482 "News support; your News accounts are "
1483 "disabled.\n\n"
1484 "You probably need to "
1485 "install libetpan and recompile "
1486 "Claws Mail."));
1489 static Folder *news_folder_new(const gchar *name, const gchar *path)
1491 warn_etpan();
1492 return NULL;
1494 void news_group_list_free(GSList *group_list)
1496 warn_etpan();
1498 void news_remove_group_list_cache(Folder *folder)
1500 warn_etpan();
1502 int news_folder_locked(Folder *folder)
1504 warn_etpan();
1505 return 0;
1507 gint news_post(Folder *folder, const gchar *file)
1509 warn_etpan();
1510 return -1;
1513 gint news_cancel_article(Folder * folder, MsgInfo * msginfo)
1515 warn_etpan();
1516 return -1;
1519 GSList *news_get_group_list(Folder *folder)
1521 warn_etpan();
1522 return NULL;
1526 FolderClass *news_get_class(void)
1528 if (news_class.idstr == NULL) {
1529 news_class.type = F_NEWS;
1530 news_class.idstr = "news";
1531 news_class.uistr = "News";
1533 /* Folder functions */
1534 news_class.new_folder = news_folder_new;
1537 return &news_class;
1540 void nntp_disconnect_all(gboolean have_connectivity)
1544 #endif