2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2001 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 2 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, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
39 #include "procheader.h"
42 #include "statusbar.h"
45 #include "prefs_common.h"
46 #include "prefs_account.h"
47 #include "inputdialog.h"
48 #include "alertpanel.h"
52 static Session *news_session_new (const gchar *server,
57 static gint news_get_article_cmd (NNTPSession *session,
61 static gint news_get_article (NNTPSession *session,
64 static gint news_get_header (NNTPSession *session,
68 static gint news_select_group (NNTPSession *session,
70 static GSList *news_get_uncached_articles(NNTPSession *session,
75 static MsgInfo *news_parse_xover (const gchar *xover_str);
76 static gchar *news_parse_xhdr (const gchar *xhdr_str,
78 static GSList *news_delete_old_articles (GSList *alist,
81 static void news_delete_all_articles (FolderItem *item);
84 static Session *news_session_new(const gchar *server, gushort port,
85 const gchar *userid, const gchar *passwd)
87 gchar buf[NNTPBUFSIZE];
89 NNTPSockInfo *nntp_sock;
91 g_return_val_if_fail(server != NULL, NULL);
93 log_message(_("creating NNTP connection to %s:%d ...\n"), server, port);
96 nntp_sock = nntp_open_auth(server, port, buf, userid, passwd);
98 nntp_sock = nntp_open(server, port, buf);
99 if (nntp_sock == NULL)
102 session = g_new(NNTPSession, 1);
103 SESSION(session)->type = SESSION_NEWS;
104 SESSION(session)->server = g_strdup(server);
105 session->nntp_sock = nntp_sock;
106 SESSION(session)->sock = nntp_sock->sock;
107 SESSION(session)->connected = TRUE;
108 SESSION(session)->phase = SESSION_READY;
109 SESSION(session)->data = NULL;
110 session->group = NULL;
112 return SESSION(session);
115 void news_session_destroy(NNTPSession *session)
117 nntp_close(session->nntp_sock);
118 session->nntp_sock = NULL;
119 SESSION(session)->sock = NULL;
121 g_free(session->group);
124 static gchar *news_query_password(const gchar *server, const gchar *user)
129 message = g_strdup_printf(_("Input password for %s on %s:"),
131 pass = input_dialog_with_invisible(_("Input password"), message, NULL);
137 static Session *news_session_new_for_folder(Folder *folder)
141 const gchar *userid = NULL;
142 gchar *passwd = NULL;
144 g_return_val_if_fail(folder != NULL, NULL);
145 g_return_val_if_fail(folder->account != NULL, NULL);
147 ac = folder->account;
148 if (ac->use_nntp_auth && ac->userid && ac->userid[0]) {
150 if (ac->passwd && ac->passwd[0])
151 passwd = g_strdup(ac->passwd);
153 passwd = news_query_password(ac->nntp_server, userid);
156 session = news_session_new(ac->nntp_server,
157 ac->set_nntpport ? ac->nntpport : NNTP_PORT,
164 NNTPSession *news_session_get(Folder *folder)
166 NNTPSession *session;
168 g_return_val_if_fail(folder != NULL, NULL);
169 g_return_val_if_fail(folder->type == F_NEWS, NULL);
170 g_return_val_if_fail(folder->account != NULL, NULL);
172 if (!REMOTE_FOLDER(folder)->session) {
173 REMOTE_FOLDER(folder)->session =
174 news_session_new_for_folder(folder);
175 return NNTP_SESSION(REMOTE_FOLDER(folder)->session);
178 session = NNTP_SESSION(REMOTE_FOLDER(folder)->session);
180 if (nntp_mode(session->nntp_sock, FALSE) != NN_SUCCESS) {
181 log_warning(_("NNTP connection to %s:%d has been"
182 " disconnected. Reconnecting...\n"),
183 folder->account->nntp_server,
184 folder->account->set_nntpport ?
185 folder->account->nntpport : NNTP_PORT);
186 session_destroy(REMOTE_FOLDER(folder)->session);
187 REMOTE_FOLDER(folder)->session =
188 news_session_new_for_folder(folder);
191 return NNTP_SESSION(REMOTE_FOLDER(folder)->session);
194 GSList *news_get_article_list(Folder *folder, FolderItem *item,
198 NNTPSession *session;
200 g_return_val_if_fail(folder != NULL, NULL);
201 g_return_val_if_fail(item != NULL, NULL);
202 g_return_val_if_fail(folder->type == F_NEWS, NULL);
204 session = news_session_get(folder);
207 alist = procmsg_read_cache(item, FALSE);
208 item->last_num = procmsg_get_last_num_in_cache(alist);
209 } else if (use_cache) {
214 alist = procmsg_read_cache(item, FALSE);
216 cache_last = procmsg_get_last_num_in_cache(alist);
217 newlist = news_get_uncached_articles
218 (session, item, cache_last, &first, &last);
219 alist = news_delete_old_articles(alist, item, first);
221 alist = g_slist_concat(alist, newlist);
222 item->last_num = last;
226 alist = news_get_uncached_articles
227 (session, item, 0, NULL, &last);
228 news_delete_all_articles(item);
229 item->last_num = last;
232 procmsg_set_flags(alist, item);
239 gchar *news_fetch_msg(Folder *folder, FolderItem *item, gint num)
241 gchar *path, *filename;
244 g_return_val_if_fail(folder != NULL, NULL);
245 g_return_val_if_fail(item != NULL, NULL);
247 path = folder_item_get_path(item);
248 if (!is_dir_exist(path))
251 filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(num), NULL);
254 if (is_file_exist(filename)) {
255 debug_print(_("article %d has been already cached.\n"), num);
259 if (!REMOTE_FOLDER(folder)->session) {
264 ok = news_select_group(NNTP_SESSION(REMOTE_FOLDER(folder)->session),
266 if (ok != NN_SUCCESS) {
267 g_warning(_("can't select group %s\n"), item->path);
272 debug_print(_("getting article %d...\n"), num);
273 ok = news_get_article(NNTP_SESSION(REMOTE_FOLDER(folder)->session),
277 g_warning(_("can't read article %d\n"), num);
285 void news_scan_group(Folder *folder, FolderItem *item)
289 gint news_post(Folder *folder, const gchar *file)
291 NNTPSession *session;
295 g_return_val_if_fail(folder != NULL, -1);
296 g_return_val_if_fail(folder->type == F_NEWS, -1);
297 g_return_val_if_fail(file != NULL, -1);
299 session = news_session_get(folder);
300 if (!session) return -1;
302 if ((fp = fopen(file, "r")) == NULL) {
303 FILE_OP_ERROR(file, "fopen");
307 ok = nntp_post(session->nntp_sock, fp);
308 if (ok != NN_SUCCESS) {
309 log_warning(_("can't post article.\n"));
320 static gint news_get_article_cmd(NNTPSession *session, const gchar *cmd,
321 gint num, gchar *filename)
325 if (nntp_get_article(session->nntp_sock, cmd, num, &msgid)
329 debug_print("Message-Id = %s, num = %d\n", msgid, num);
332 if (recv_write_to_file(session->nntp_sock->sock, filename) < 0) {
333 log_warning(_("can't retrieve article %d\n"), num);
340 static gint news_get_article(NNTPSession *session, gint num, gchar *filename)
342 return news_get_article_cmd(session, "ARTICLE", num, filename);
345 static gint news_get_header(NNTPSession *session, gint num, gchar *filename)
347 return news_get_article_cmd(session, "HEAD", num, filename);
352 * @session: Active NNTP session.
353 * @group: Newsgroup name.
355 * Select newsgroup @group with the GROUP command if it is not already
356 * selected in @session.
358 * Return value: NNTP result code.
360 static gint news_select_group(NNTPSession *session, const gchar *group)
363 gint num, first, last;
365 if (session->group && g_strcasecmp(session->group, group) == 0)
368 g_free(session->group);
369 session->group = NULL;
371 ok = nntp_group(session->nntp_sock, group, &num, &first, &last);
372 if (ok == NN_SUCCESS)
373 session->group = g_strdup(group);
378 static GSList *news_get_uncached_articles(NNTPSession *session,
379 FolderItem *item, gint cache_last,
380 gint *rfirst, gint *rlast)
383 gint num = 0, first = 0, last = 0, begin = 0, end = 0;
384 gchar buf[NNTPBUFSIZE];
385 GSList *newlist = NULL;
386 GSList *llast = NULL;
389 if (rfirst) *rfirst = 0;
390 if (rlast) *rlast = 0;
392 g_return_val_if_fail(session != NULL, NULL);
393 g_return_val_if_fail(item != NULL, NULL);
394 g_return_val_if_fail(item->folder != NULL, NULL);
395 g_return_val_if_fail(item->folder->type == F_NEWS, NULL);
397 g_free(session->group);
398 session->group = NULL;
400 ok = nntp_group(session->nntp_sock, item->path,
401 &num, &first, &last);
402 if (ok != NN_SUCCESS) {
403 log_warning(_("can't set group: %s\n"), item->path);
406 session->group = g_strdup(item->path);
408 /* calculate getting overview range */
410 log_warning(_("invalid article range: %d - %d\n"),
414 if (cache_last < first)
416 else if (last < cache_last)
418 else if (last == cache_last) {
419 debug_print(_("no new articles.\n"));
422 begin = cache_last + 1;
425 if (rfirst) *rfirst = first;
426 if (rlast) *rlast = last;
428 if (prefs_common.max_articles > 0 &&
429 end - begin + 1 > prefs_common.max_articles)
430 begin = end - prefs_common.max_articles + 1;
432 log_message(_("getting xover %d - %d in %s...\n"),
433 begin, end, item->path);
434 if (nntp_xover(session->nntp_sock, begin, end) != NN_SUCCESS) {
435 log_warning(_("can't get xover\n"));
440 if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
441 log_warning(_("error occurred while getting xover.\n"));
445 if (buf[0] == '.' && buf[1] == '\r') break;
447 msginfo = news_parse_xover(buf);
449 log_warning(_("invalid xover line: %s\n"), buf);
453 msginfo->folder = item;
454 msginfo->flags = MSG_NEW|MSG_UNREAD|MSG_NEWS;
455 msginfo->newsgroups = g_strdup(item->path);
458 llast = newlist = g_slist_append(newlist, msginfo);
460 llast = g_slist_append(llast, msginfo);
465 if (nntp_xhdr(session->nntp_sock, "to", begin, end) != NN_SUCCESS) {
466 log_warning(_("can't get xhdr\n"));
473 if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
474 log_warning(_("error occurred while getting xhdr.\n"));
478 if (buf[0] == '.' && buf[1] == '\r') break;
480 g_warning("llast == NULL\n");
484 msginfo = (MsgInfo *)llast->data;
485 msginfo->to = news_parse_xhdr(buf, msginfo);
490 if (nntp_xhdr(session->nntp_sock, "cc", begin, end) != NN_SUCCESS) {
491 log_warning(_("can't get xhdr\n"));
498 if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) < 0) {
499 log_warning(_("error occurred while getting xhdr.\n"));
503 if (buf[0] == '.' && buf[1] == '\r') break;
505 g_warning("llast == NULL\n");
509 msginfo = (MsgInfo *)llast->data;
510 msginfo->cc = news_parse_xhdr(buf, msginfo);
518 #define PARSE_ONE_PARAM(p, srcp) \
520 p = strchr(srcp, '\t'); \
521 if (!p) return NULL; \
526 static MsgInfo *news_parse_xover(const gchar *xover_str)
529 gchar buf[NNTPBUFSIZE];
530 gchar *subject, *sender, *size, *line, *date, *msgid, *ref, *tmp;
532 gint num, size_int, line_int;
535 Xalloca(xover_buf, strlen(xover_str) + 1, return NULL);
536 strcpy(xover_buf, xover_str);
538 PARSE_ONE_PARAM(subject, xover_buf);
539 PARSE_ONE_PARAM(sender, subject);
540 PARSE_ONE_PARAM(date, sender);
541 PARSE_ONE_PARAM(msgid, date);
542 PARSE_ONE_PARAM(ref, msgid);
543 PARSE_ONE_PARAM(size, ref);
544 PARSE_ONE_PARAM(line, size);
546 tmp = strchr(line, '\t');
547 if (!tmp) tmp = strchr(line, '\r');
548 if (!tmp) tmp = strchr(line, '\n');
549 if (tmp) *tmp = '\0';
551 num = atoi(xover_str);
552 size_int = atoi(size);
553 line_int = atoi(line);
556 msginfo = g_new0(MsgInfo, 1);
557 msginfo->msgnum = num;
558 msginfo->size = size_int;
560 msginfo->date = g_strdup(date);
561 msginfo->date_t = procheader_date_parse(NULL, date, 0);
563 conv_unmime_header(buf, sizeof(buf), sender, NULL);
564 msginfo->from = g_strdup(buf);
565 msginfo->fromname = procheader_get_fromname(buf);
567 conv_unmime_header(buf, sizeof(buf), subject, NULL);
568 msginfo->subject = g_strdup(buf);
570 extract_parenthesis(msgid, '<', '>');
573 msginfo->msgid = g_strdup(msgid);
575 msginfo->references = g_strdup(ref);
576 eliminate_parenthesis(ref, '(', ')');
577 if ((p = strrchr(ref, '<')) != NULL) {
578 extract_parenthesis(p, '<', '>');
581 msginfo->inreplyto = g_strdup(p);
587 static gchar *news_parse_xhdr(const gchar *xhdr_str, MsgInfo *msginfo)
589 gchar buf[NNTPBUFSIZE];
594 p = strchr(xhdr_str, ' ');
600 num = atoi(xhdr_str);
601 if (msginfo->msgnum != num) return NULL;
603 tmp = strchr(p, '\r');
604 if (!tmp) tmp = strchr(p, '\n');
607 return g_strndup(p, tmp - p);
612 static GSList *news_delete_old_articles(GSList *alist, FolderItem *item,
619 g_return_val_if_fail(item != NULL, alist);
620 g_return_val_if_fail(item->folder != NULL, alist);
621 g_return_val_if_fail(item->folder->type == F_NEWS, alist);
623 if (first < 2) return alist;
625 debug_print(_("Deleting cached articles 1 - %d ... "), first - 1);
627 dir = folder_item_get_path(item);
628 remove_numbered_files(dir, 1, first - 1);
631 for (cur = alist; cur != NULL; ) {
634 msginfo = (MsgInfo *)cur->data;
635 if (msginfo && msginfo->msgnum < first) {
636 procmsg_msginfo_free(msginfo);
637 alist = g_slist_remove(alist, msginfo);
646 static void news_delete_all_articles(FolderItem *item)
650 g_return_if_fail(item != NULL);
651 g_return_if_fail(item->folder != NULL);
652 g_return_if_fail(item->folder->type == F_NEWS);
654 debug_print(_("\tDeleting all cached articles... "));
656 dir = folder_item_get_path(item);
657 if (!is_dir_exist(dir))
659 remove_all_numbered_files(dir);
662 debug_print(_("done.\n"));
666 news_get_group_list returns a strings list.
667 These strings are the names of the newsgroups of a server.
668 item is the FolderItem of the news server.
669 The names of the newsgroups are cached into a file so that
670 when the function is called again, there is no need to make
671 a request to the server.
674 GSList * news_get_group_list(FolderItem *item)
676 gchar *path, *filename;
678 NNTPSession *session;
679 GSList * group_list = NULL;
681 gchar buf[NNTPBUFSIZE];
687 path = folder_item_get_path(item);
689 if (!is_dir_exist(path))
692 filename = g_strconcat(path, G_DIR_SEPARATOR_S, GROUPLIST_FILE, NULL);
695 session = news_session_get(item->folder);
700 if (is_file_exist(filename)) {
701 debug_print(_("group list has been already cached.\n"));
704 ok = nntp_list(session->nntp_sock);
705 if (ok != NN_SUCCESS)
708 if (recv_write_to_file(SESSION(session)->sock, filename) < 0) {
709 log_warning(_("can't retrieve group list\n"));
714 f = fopen(filename, "r");
715 while (fgets(buf, NNTPBUFSIZE, f)) {
719 while ((buf[len] != 0) && (buf[len] != ' '))
724 group_list = g_slist_append(group_list, s);
729 group_list = g_slist_sort(group_list, (GCompareFunc) g_strcasecmp);
735 remove the cache file of the names of the newsgroups.
738 void news_reset_group_list(FolderItem *item)
740 gchar *path, *filename;
742 debug_print(_("\tDeleting cached group list... "));
743 path = folder_item_get_path(item);
744 if (!is_dir_exist(path))
747 filename = g_strconcat(path, G_DIR_SEPARATOR_S, GROUPLIST_FILE, NULL);
749 if (remove(filename) != 0)
750 log_warning(_("can't delete cached group list %s\n"), filename);