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/>.
21 #include "claws-features.h"
29 #include <glib/gi18n.h>
35 #include <libetpan/libetpan.h>
37 #include "nntp-thread.h"
43 #include "procheader.h"
46 #include "statusbar.h"
49 #include "passwordstore.h"
50 #include "prefs_common.h"
51 #include "prefs_account.h"
52 #include "inputdialog.h"
54 #include "progressindicator.h"
55 #include "remotefolder.h"
56 #include "alertpanel.h"
63 #include "file-utils.h"
67 #define NNTPS_PORT 563
70 typedef struct _NewsFolder NewsFolder
;
71 typedef struct _NewsSession NewsSession
;
73 #define NEWS_FOLDER(obj) ((NewsFolder *)obj)
74 #define NEWS_SESSION(obj) ((NewsSession *)obj)
92 static void news_folder_init(Folder
*folder
, const gchar
*name
,
95 static Folder
*news_folder_new (const gchar
*name
,
97 static void news_folder_destroy (Folder
*folder
);
99 static gchar
*news_fetch_msg (Folder
*folder
,
102 static void news_remove_cached_msg (Folder
*folder
,
106 static Session
*news_session_new (Folder
*folder
,
107 const PrefsAccount
*account
,
111 static Session
*news_session_new (Folder
*folder
,
112 const PrefsAccount
*account
,
116 static gint
news_get_article (Folder
*folder
,
120 static gint
news_select_group (Folder
*folder
,
125 static MsgInfo
*news_parse_xover (struct newsnntp_xover_resp_item
*item
);
126 static gint
news_get_num_list (Folder
*folder
,
129 gboolean
*old_uids_valid
);
130 static MsgInfo
*news_get_msginfo (Folder
*folder
,
133 static GSList
*news_get_msginfos (Folder
*folder
,
135 GSList
*msgnum_list
);
136 static gboolean
news_scan_required (Folder
*folder
,
139 static gchar
*news_folder_get_path (Folder
*folder
);
140 static gchar
*news_item_get_path (Folder
*folder
,
142 static void news_synchronise (FolderItem
*item
, gint days
);
143 static int news_remove_msg (Folder
*folder
,
146 static gint
news_rename_folder (Folder
*folder
,
149 static gint
news_remove_folder (Folder
*folder
,
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
;
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
,
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
))
214 filename
= g_strconcat(path
, G_DIR_SEPARATOR_S
, itos(msgnum
), NULL
);
216 claws_unlink(filename
);
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
)
237 return NEWS_FOLDER(folder
)->lock_count
;
240 static Folder
*news_folder_new(const gchar
*name
, const gchar
*path
)
244 folder
= (Folder
*)g_new0(NewsFolder
, 1);
245 folder
->klass
= &news_class
;
246 news_folder_init(folder
, name
, path
);
251 static void news_folder_destroy(Folder
*folder
)
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
);
264 folder_remote_folder_destroy(REMOTE_FOLDER(folder
));
267 static void news_folder_init(Folder
*folder
, const gchar
*name
,
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
);
290 if (session
->state
!= SESSION_READY
|| news_folder_locked(news_session
->folder
))
293 news_folder_lock(NEWS_FOLDER(news_session
->folder
));
295 if ((r
= nntp_threaded_date(news_session
->folder
, <
)) != 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"
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
);
312 news_folder_unlock(NEWS_FOLDER(news_session
->folder
));
313 session_set_access_time(session
);
319 static Session
*news_session_new(Folder
*folder
, const PrefsAccount
*account
, gushort port
,
322 static Session
*news_session_new(Folder
*folder
, const PrefsAccount
*account
, gushort port
)
325 NewsSession
*session
;
326 const char *server
= account
->nntp_server
;
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
);
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
;
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
);
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
));
375 session
->folder
= folder
;
377 return SESSION(session
);
380 static Session
*news_session_new_for_folder(Folder
*folder
)
384 const gchar
*userid
= NULL
;
385 gchar
*passwd
= NULL
;
389 cm_return_val_if_fail(folder
!= NULL
, NULL
);
390 cm_return_val_if_fail(folder
->account
!= NULL
, NULL
);
392 ac
= folder
->account
;
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
);
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 "
407 NULL
, _("_Cancel"), NULL
, _("Con_tinue connecting"),
408 NULL
, NULL
, ALERTFOCUS_FIRST
, FALSE
, NULL
, ALERT_WARNING
) != G_ALERTALTERNATE
)
411 port
= ac
->set_nntpport
? ac
->nntpport
: NNTP_PORT
;
412 session
= news_session_new(folder
, ac
, port
);
415 if (ac
->use_nntp_auth
&& ac
->userid
&& ac
->userid
[0]) {
417 if (password_get(userid
, ac
->nntp_server
, "nntp", port
, &passwd
)) {
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
,
423 &(ac
->session_passwd
));
428 r
= nntp_threaded_mode_reader(folder
);
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.
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"));
461 /* An error state bail out */
462 log_error(LOG_PROTOCOL
, _("Error creating session with %s:%d\n"), ac
->nntp_server
, port
);
464 session_destroy(SESSION(session
));
466 if (ac
->session_passwd
) {
467 g_free(ac
->session_passwd
);
468 ac
->session_passwd
= NULL
;
474 if ((session
!= NULL
) && ac
->use_nntp_auth
) { /* FIXME: && ac->use_nntp_auth_onconnect */
475 if (nntp_threaded_login(folder
, userid
, passwd
) !=
477 log_error(LOG_PROTOCOL
, _("Error authenticating to %s:%d...\n"), ac
->nntp_server
, port
);
478 session_destroy(SESSION(session
));
480 if (ac
->session_passwd
) {
481 g_free(ac
->session_passwd
);
482 ac
->session_passwd
= NULL
;
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."))) {
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
);
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
);
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
)) {
550 filename
= g_strconcat(path
, G_DIR_SEPARATOR_S
, itos(msginfo
->msgnum
), NULL
);
553 if (is_file_exist(filename
)) {
554 claws_unlink(filename
);
559 static gchar
*news_fetch_msg(Folder
*folder
, FolderItem
*item
, gint num
)
561 gchar
*path
, *filename
;
562 NewsSession
*session
;
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
))
571 filename
= g_strconcat(path
, G_DIR_SEPARATOR_S
, itos(num
), NULL
);
574 if (is_file_exist(filename
)) {
575 debug_print("article %d has been already cached.\n", num
);
579 session
= news_session_get(folder
);
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
;
595 debug_print("getting article %d...\n", num
);
596 ok
= news_get_article(folder
,
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
;
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
;
625 static void news_group_info_free(NewsGroupInfo
*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
;
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
))
651 filename
= g_strconcat(path
, G_DIR_SEPARATOR_S
, NEWSGROUP_LIST
, NULL
);
654 if ((fp
= claws_fopen(filename
, "rb")) == NULL
) {
655 NewsSession
*session
;
657 clist
*grouplist
= NULL
;
659 fp
= claws_fopen(filename
, "wb");
665 session
= news_session_get(folder
);
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
;
685 for (cur
= clist_begin(grouplist
); cur
; cur
= clist_next(cur
)) {
686 struct newsnntp_group_info
*info
= (struct newsnntp_group_info
*)
688 if (fprintf(fp
, "%s %d %d %c\n",
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
;
698 newsnntp_list_free(grouplist
);
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
;
712 if ((fp
= claws_fopen(filename
, "rb")) == NULL
) {
713 FILE_OP_ERROR(filename
, "claws_fopen");
719 while (claws_fgets(buf
, sizeof(buf
), fp
) != NULL
) {
725 NewsGroupInfo
*ginfo
;
733 if (sscanf(p
, "%d %d %c", &last_num
, &first_num
, &type
) < 3)
736 ginfo
= news_group_info_new(name
, first_num
, last_num
, type
);
739 last
= list
= g_slist_append(NULL
, ginfo
);
741 last
= g_slist_append(last
, ginfo
);
749 list
= g_slist_sort(list
, (GCompareFunc
)news_group_info_compare
);
754 void news_group_list_free(GSList
*group_list
)
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
);
776 if (is_file_exist(filename
)) {
777 if (claws_unlink(filename
) < 0)
778 FILE_OP_ERROR(filename
, "remove");
783 gint
news_post(Folder
*folder
, const gchar
*file
)
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
);
799 ok
= nntp_threaded_post(folder
, contents
, strlen(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
)
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
);
824 mmap_string_unref(result
);
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
)
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
;
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
);
873 log_warning(LOG_PROTOCOL
, _("couldn't select group: %s\n"), group
);
878 static MsgInfo
*news_parse_xover(struct newsnntp_xover_resp_item
*item
)
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
, '<', '>');
904 msginfo
->msgid
= g_strdup(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);
917 while (ref_tokens
[i
]) {
918 gchar
*cur_ref
= ref_tokens
[i
];
919 msginfo
->references
= references_list_append(msginfo
->references
,
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
, '<', '>');
931 msginfo
->inreplyto
= g_strdup(p
);
939 gint
news_cancel_article(Folder
* folder
, MsgInfo
* msginfo
)
943 gchar date
[RFC822_DATE_BUFFSIZE
];
945 tmp
= g_strdup_printf("%s%ccancel%p", get_tmp_dir(),
946 G_DIR_SEPARATOR
, msginfo
);
950 if ((tmpfp
= claws_fopen(tmp
, "wb")) == NULL
) {
951 FILE_OP_ERROR(tmp
, "claws_fopen");
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
));
963 get_rfc822_date(date
, sizeof(date
));
964 if (fprintf(tmpfp
, "From: %s\r\n"
966 "Subject: cmsg cancel <%s>\r\n"
967 "Control: cancel <%s>\r\n"
969 "X-Cancelled-by: %s\r\n"
972 "removed with Claws Mail\r\n",
980 FILE_OP_ERROR(tmp
, "fprintf");
987 if (claws_safe_fclose(tmpfp
) == EOF
) {
988 FILE_OP_ERROR(tmp
, "claws_fclose");
994 news_post(folder
, tmp
);
1002 static gchar
*news_folder_get_path(Folder
*folder
)
1006 cm_return_val_if_fail(folder
->account
!= NULL
, NULL
);
1008 folder_path
= g_strconcat(get_news_cache_dir(),
1010 folder
->account
->nntp_server
,
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
)) {
1026 path
= g_strconcat(folder_path
, G_DIR_SEPARATOR_S
,
1029 path
= g_strdup(folder_path
);
1032 path
= g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S
,
1033 folder_path
, G_DIR_SEPARATOR_S
,
1036 path
= g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S
,
1039 g_free(folder_path
);
1041 while (strchr(path
, '/'))
1042 *strchr(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;
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
));
1071 dir
= news_folder_get_path(folder
);
1073 remove_all_numbered_files(dir
);
1074 else if (last
< first
)
1075 log_warning(LOG_PROTOCOL
, _("invalid article range: %d - %d\n"),
1078 for (i
= first
; i
<= last
; i
++) {
1079 *msgnum_list
= g_slist_prepend(*msgnum_list
,
1080 GINT_TO_POINTER(i
));
1083 debug_print("removing old messages from %d to %d in %s\n",
1085 remove_numbered_files(dir
, 1, first
- 1);
1088 news_folder_unlock(NEWS_FOLDER(item
->folder
));
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
);
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
;
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
;
1117 clist
*hdrlist
= NULL
;
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
)
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
);
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
);
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
));
1168 if (msginfo
->newsgroups
)
1169 g_free(msginfo
->newsgroups
);
1170 msginfo
->newsgroups
= g_strdup(hdrval
->hdr_value
);
1173 newsnntp_xhdr_free(hdrlist
);
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
);
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
));
1196 g_free(msginfo
->to
);
1197 msginfo
->to
= g_strdup(hdrval
->hdr_value
);
1200 newsnntp_xhdr_free(hdrlist
);
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
);
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
));
1223 g_free(msginfo
->cc
);
1224 msginfo
->cc
= g_strdup(hdrval
->hdr_value
);
1227 newsnntp_xhdr_free(hdrlist
);
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
;
1240 clist
*msglist
= NULL
;
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
));
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
);
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
);
1277 log_warning(LOG_PROTOCOL
, _("invalid xover line\n"));
1281 msginfo
->folder
= item
;
1282 news_set_msg_flags(item
, msginfo
);
1283 msginfo
->flags
.tmp_flags
|= MSG_NEWS
;
1286 llast
= newlist
= g_slist_append(newlist
, msginfo
);
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
);
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
);
1319 msginfo
= msglist
->data
;
1321 g_slist_free(msglist
);
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
);
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
);
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
)
1377 void news_synchronise(FolderItem
*item
, gint days
)
1379 news_gtk_synchronise(item
, days
);
1382 static gint
news_rename_folder(Folder
*folder
, FolderItem
*item
,
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
);
1397 item
->name
= g_strdup(name
);
1402 static gint
news_remove_folder(Folder
*folder
, FolderItem
*item
)
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
);
1418 folder_item_remove(item
);
1422 void nntp_disconnect_all(gboolean have_connectivity
)
1425 gboolean short_timeout
;
1426 #ifdef HAVE_NETWORKMANAGER_SUPPORT
1430 #ifdef HAVE_NETWORKMANAGER_SUPPORT
1432 short_timeout
= !networkmanager_is_online(&error
);
1434 short_timeout
= TRUE
;
1435 g_error_free(error
);
1438 short_timeout
= TRUE
;
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
;
1461 nntp_main_set_timeout(prefs_common
.io_timeout_secs
);
1466 #include <glib/gi18n.h>
1467 #include <gtk/gtk.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
;
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 "
1484 "You probably need to "
1485 "install libetpan and recompile "
1489 static Folder
*news_folder_new(const gchar
*name
, const gchar
*path
)
1494 void news_group_list_free(GSList
*group_list
)
1498 void news_remove_group_list_cache(Folder
*folder
)
1502 int news_folder_locked(Folder
*folder
)
1507 gint
news_post(Folder
*folder
, const gchar
*file
)
1513 gint
news_cancel_article(Folder
* folder
, MsgInfo
* msginfo
)
1519 GSList
*news_get_group_list(Folder
*folder
)
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
;
1540 void nntp_disconnect_all(gboolean have_connectivity
)