Implement SSL certificate verification option (default, and per-feed).
[claws.git] / src / plugins / rssyl / rssyl.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2004 Hiroyuki Yamamoto
4  * This file (C) 2005 Andrej Kacian <andrej@kacian.sk>
5  *
6  * - s-c folderclass callback handler functions
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #  include "config.h"
25 #include "claws-features.h"
26 #endif
27
28 #include <glib.h>
29 #include <glib/gi18n.h>
30
31 #ifdef G_OS_WIN32
32 #  include <w32lib.h>
33 #endif
34
35 #include <glib.h>
36 #include <curl/curl.h>
37
38 #include "folder.h"
39 #include "localfolder.h"
40
41 #include "procheader.h"
42 #include "common/utils.h"
43 #include "toolbar.h"
44 #include "prefs_toolbar.h"
45
46 #include "main.h"
47
48 #include "feed.h"
49 #include "feedprops.h"
50 #include "opml.h"
51 #include "rssyl.h"
52 #include "rssyl_gtk.h"
53 #include "rssyl_prefs.h"
54 #include "strreplace.h"
55
56 static gint rssyl_create_tree(Folder *folder);
57
58 static gboolean existing_tree_found = FALSE;
59
60 static void rssyl_init_read_func(FolderItem *item, gpointer data)
61 {
62         if( !IS_RSSYL_FOLDER_ITEM(item) )
63                 return;
64
65         existing_tree_found = TRUE;
66
67         if( folder_item_parent(item) == NULL )
68                 return;
69
70         rssyl_get_feed_props((RSSylFolderItem *)item);
71 }
72
73 static void rssyl_make_rc_dir(void)
74 {
75         gchar *rssyl_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RSSYL_DIR,
76                         NULL);
77
78         if( !is_dir_exist(rssyl_dir) ) {
79                 if( make_dir(rssyl_dir) < 0 ) {
80                         g_warning("couldn't create directory %s\n", rssyl_dir);
81                 }
82
83                 debug_print("created directorty %s\n", rssyl_dir);
84         }
85
86         g_free(rssyl_dir);
87 }
88
89 static void rssyl_create_default_mailbox(void)
90 {
91         Folder *root = NULL;
92         FolderItem *item;
93
94         rssyl_make_rc_dir();
95
96         root = folder_new(rssyl_folder_get_class(), RSSYL_DEFAULT_MAILBOX, NULL);
97
98         g_return_if_fail(root != NULL);
99         folder_add(root);
100
101         item = FOLDER_ITEM(root->node->data);
102
103         rssyl_subscribe_new_feed(item, RSSYL_DEFAULT_FEED, TRUE);
104 }
105
106 static gboolean rssyl_refresh_all_feeds_deferred(gpointer data)
107 {
108         rssyl_refresh_all_feeds();
109         return FALSE;
110 }
111
112 static void rssyl_toolbar_cb_refresh_all(gpointer parent, const gchar *item_name, gpointer data)
113 {
114         rssyl_refresh_all_feeds();
115 }
116
117 void rssyl_init(void)
118 {
119         folder_register_class(rssyl_folder_get_class());
120
121         rssyl_gtk_init();
122
123         rssyl_make_rc_dir();
124
125         rssyl_prefs_init();
126
127         folder_func_to_all_folders((FolderItemFunc)rssyl_init_read_func, NULL);
128
129         if( existing_tree_found == FALSE )
130                 rssyl_create_default_mailbox();
131
132         prefs_toolbar_register_plugin_item(TOOLBAR_MAIN, "RSSyl",
133                         _("Refresh all feeds"), rssyl_toolbar_cb_refresh_all, NULL);
134
135         rssyl_opml_export();
136
137         if( rssyl_prefs_get()->refresh_on_startup &&
138                         claws_is_starting() )
139                 g_timeout_add(2000, rssyl_refresh_all_feeds_deferred, NULL);
140 }
141
142 void rssyl_done(void)
143 {
144         prefs_toolbar_unregister_plugin_item(TOOLBAR_MAIN, "RSSyl",
145                         _("Refresh all feeds"));
146         rssyl_prefs_done();
147         rssyl_gtk_done();
148         if (!claws_is_exiting())
149                 folder_unregister_class(rssyl_folder_get_class());
150 }
151
152 static gchar *rssyl_get_new_msg_filename(FolderItem *dest)
153 {
154         gchar *destfile;
155         gchar *destpath;
156
157         destpath = folder_item_get_path(dest);
158         g_return_val_if_fail(destpath != NULL, NULL);
159
160         if( !is_dir_exist(destpath) )
161                 make_dir_hier(destpath);
162
163         for( ; ; ) {
164                 destfile = g_strdup_printf("%s%c%d", destpath, G_DIR_SEPARATOR,
165                                 dest->last_num + 1);
166                 if( is_file_entry_exist(destfile) ) {
167                         dest->last_num++;
168                         g_free(destfile);
169                 } else
170                         break;
171         }
172
173         g_free(destpath);
174
175         return destfile;
176 }
177
178 static void rssyl_get_last_num(Folder *folder, FolderItem *item)
179 {
180         gchar *path;
181         DIR *dp;
182         struct dirent *d;
183         gint max = 0;
184         gint num;
185
186         g_return_if_fail(item != NULL);
187
188         debug_print("rssyl_get_last_num(): Scanning %s ...\n", item->path);
189         path = folder_item_get_path(item);
190         g_return_if_fail(path != NULL);
191         if( change_dir(path) < 0 ) {
192                 g_free(path);
193                 return;
194         }
195         g_free(path);
196
197         if( (dp = opendir(".")) == NULL ) {
198                 FILE_OP_ERROR(item->path, "opendir");
199                 return;
200         }
201
202         while( (d = readdir(dp)) != NULL ) {
203                 if( (num = to_number(d->d_name)) > 0 && dirent_is_regular_file(d) ) {
204                         if( max < num )
205                                 max = num;
206                 }
207         }
208         closedir(dp);
209
210         debug_print("Last number in dir %s = %d\n", item->path, max);
211         item->last_num = max;
212 }
213
214 struct _RSSylFolder {
215         LocalFolder folder;
216 };
217
218 typedef struct _RSSylFolder RSSylFolder;
219
220 FolderClass rssyl_class;
221
222 static Folder *rssyl_new_folder(const gchar *name, const gchar *path)
223 {
224         RSSylFolder *folder;
225
226         debug_print("RSSyl: new_folder\n");
227
228         rssyl_make_rc_dir();
229
230         folder = g_new0(RSSylFolder, 1);
231         FOLDER(folder)->klass = &rssyl_class;
232         folder_init(FOLDER(folder), name);
233
234         return FOLDER(folder);
235 }
236
237 static void rssyl_destroy_folder(Folder *_folder)
238 {
239         RSSylFolder *folder = (RSSylFolder *)_folder;
240
241         folder_local_folder_destroy(LOCAL_FOLDER(folder));
242 }
243
244 static gint rssyl_scan_tree(Folder *folder)
245 {
246         g_return_val_if_fail(folder != NULL, -1);
247
248         folder->outbox = NULL;
249         folder->draft = NULL;
250         folder->queue = NULL;
251         folder->trash = NULL;
252
253         debug_print("RSSyl: scanning tree\n");
254         rssyl_create_tree(folder);
255
256         return 0;
257 }
258
259 static gint rssyl_create_tree(Folder *folder)
260 {
261         FolderItem *rootitem;
262         GNode *rootnode;
263
264         rssyl_make_rc_dir();
265
266         if( !folder->node ) {
267                 rootitem = folder_item_new(folder, folder->name, NULL);
268                 rootitem->folder = folder;
269                 rootnode = g_node_new(rootitem);
270                 folder->node = rootnode;
271                 rootitem->node = rootnode;
272         } else {
273                 rootitem = FOLDER_ITEM(folder->node->data);
274                 rootnode = folder->node;
275         }
276
277         debug_print("RSSyl: created new rssyl tree\n");
278         return 0;
279 }
280
281 static FolderItem *rssyl_item_new(Folder *folder)
282 {
283         RSSylFolderItem *ritem;
284
285         debug_print("RSSyl: item_new\n");
286
287         ritem = g_new0(RSSylFolderItem, 1);
288
289         ritem->url = NULL;
290         ritem->default_refresh_interval = TRUE;
291         ritem->default_expired_num = TRUE;
292         ritem->fetch_comments = FALSE;
293         ritem->fetch_comments_for = -1;
294         ritem->silent_update = 0;
295         ritem->refresh_interval = rssyl_prefs_get()->refresh;
296         ritem->refresh_id = 0;
297         ritem->expired_num = rssyl_prefs_get()->expired;
298         ritem->last_count = 0;
299         ritem->ssl_verify_peer = rssyl_prefs_get()->ssl_verify_peer;
300
301         ritem->contents = NULL;
302         ritem->feedprop = NULL;
303
304         return (FolderItem *)ritem;
305 }
306
307 static void rssyl_item_destroy(Folder *folder, FolderItem *item)
308 {
309         RSSylFolderItem *ritem = (RSSylFolderItem *)item;
310
311         g_return_if_fail(ritem != NULL);
312
313         /* Silently remove feed refresh timeouts */
314         if( ritem->refresh_id != 0 )
315                 g_source_remove(ritem->refresh_id);
316
317         g_free(ritem->url);
318         g_free(ritem->official_name);
319         g_slist_free(ritem->contents);
320
321         g_free(item);
322 }
323
324 static FolderItem *rssyl_create_folder(Folder *folder,
325                                                                 FolderItem *parent, const gchar *name)
326 {
327         gchar *path = NULL, *tmp;
328         FolderItem *newitem = NULL;
329
330         g_return_val_if_fail(folder != NULL, NULL);
331         g_return_val_if_fail(parent != NULL, NULL);
332         g_return_val_if_fail(name != NULL, NULL);
333         tmp = rssyl_feed_title_to_dir((gchar *)name);
334         path = g_strconcat((parent->path != NULL) ? parent->path : "", ".",
335                                 tmp, NULL);
336         g_free(tmp);
337         newitem = folder_item_new(folder, name, path);
338         folder_item_append(parent, newitem);
339         g_free(path);
340
341         return newitem;
342 }
343
344 static gchar *rssyl_item_get_path(Folder *folder, FolderItem *item)
345 {
346         gchar *result, *tmp;
347         tmp = rssyl_feed_title_to_dir(item->name);
348         result = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RSSYL_DIR,
349                         G_DIR_SEPARATOR_S, tmp, NULL);
350         g_free(tmp);
351         return result;
352 }
353
354 static gint rssyl_rename_folder(Folder *folder, FolderItem *item,
355                                 const gchar *name)
356 {
357         gchar *oldname = NULL, *oldpath = NULL, *newpath = NULL;
358         RSSylFolderItem *ritem = NULL;
359         g_return_val_if_fail(folder != NULL, -1);
360         g_return_val_if_fail(item != NULL, -1);
361         g_return_val_if_fail(item->path != NULL, -1);
362         g_return_val_if_fail(name != NULL, -1);
363
364         debug_print("RSSyl: renaming folder '%s' to '%s'\n", item->path, name);
365
366         oldpath = rssyl_item_get_path(folder, item);
367         
368         /* now get the new path using the new name */
369         oldname = item->name;
370         item->name = g_strdup(name);
371         newpath = rssyl_item_get_path(folder, item);
372         
373         /* put back the old name in case the rename fails */
374         g_free(item->name);
375         item->name = oldname;
376         
377         if (g_rename(oldpath, newpath) < 0) {
378                 FILE_OP_ERROR(oldpath, "rename");
379                 g_free(oldpath);
380                 g_free(newpath);
381                 return -1;
382         }
383         
384         g_free(item->path);
385         item->path = g_strdup_printf(".%s", name);
386         
387         ritem = (RSSylFolderItem *)item;
388         
389         if (ritem->url)
390                 rssyl_props_update_name(ritem, (gchar *)name);
391         
392         g_free(item->name);
393         item->name = g_strdup(name);
394         
395         folder_write_list();
396
397         return 0;
398 }
399
400 static gint rssyl_remove_folder(Folder *folder, FolderItem *item)
401 {
402         g_return_val_if_fail(folder != NULL, -1);
403         g_return_val_if_fail(item != NULL, -1);
404         g_return_val_if_fail(item->path != NULL, -1);
405         g_return_val_if_fail(item->stype == F_NORMAL, -1);
406
407         debug_print("RSSyl: removing folder item %s\n", item->path);
408
409         folder_item_remove(item);
410
411         return 0;
412 }
413
414 static gint rssyl_get_num_list(Folder *folder, FolderItem *item,
415                 MsgNumberList **list, gboolean *old_uids_valid)
416 {
417         gchar *path;
418         DIR *dp;
419         struct dirent *d;
420         gint num, nummsgs = 0;
421         RSSylFolderItem *ritem = (RSSylFolderItem *)item;
422
423         g_return_val_if_fail(item != NULL, -1);
424
425         debug_print("RSSyl: scanning '%s'...\n", item->path);
426
427         rssyl_get_feed_props(ritem);
428
429         if (ritem->url == NULL)
430                 return -1;
431
432         *old_uids_valid = TRUE;
433
434         path = folder_item_get_path(item);
435         g_return_val_if_fail(path != NULL, -1);
436         if( change_dir(path) < 0 ) {
437                 g_free(path);
438                 return -1;
439         }
440         g_free(path);
441
442         if( (dp = opendir(".")) == NULL ) {
443                 FILE_OP_ERROR(item->path, "opendir");
444                 return -1;
445         }
446
447         while( (d = readdir(dp)) != NULL ) {
448                 if( (num = to_number(d->d_name)) > 0 ) {
449                         *list = g_slist_prepend(*list, GINT_TO_POINTER(num));
450                         nummsgs++;
451                 }
452         }
453         closedir(dp);
454
455         return nummsgs;
456 }
457
458 static gboolean rssyl_scan_required(Folder *folder, FolderItem *item)
459 {
460         return TRUE;
461 }
462
463 static gchar *rssyl_fetch_msg(Folder *folder, FolderItem *item, gint num)
464 {
465         gchar *snum = g_strdup_printf("%d", num);
466         gchar *tmp = rssyl_feed_title_to_dir(item->name);
467         gchar *file = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, RSSYL_DIR,
468                         G_DIR_SEPARATOR_S, tmp,
469                         G_DIR_SEPARATOR_S, snum, NULL);
470         g_free(tmp);
471         debug_print("RSSyl: fetch_msg: '%s'\n", file);
472
473         g_free(snum);
474
475         return file;
476 }
477
478 static MsgInfo *rssyl_get_msginfo(Folder *folder, FolderItem *item, gint num)
479 {
480         MsgInfo *msginfo = NULL;
481         gchar *file;
482         MsgFlags flags;
483
484         debug_print("RSSyl: get_msginfo: %d\n", num);
485
486         g_return_val_if_fail(folder != NULL, NULL);
487         g_return_val_if_fail(item != NULL, NULL);
488         g_return_val_if_fail(num > 0, NULL);
489
490         file = rssyl_fetch_msg(folder, item, num);
491         g_return_val_if_fail(file != NULL, NULL);
492
493         flags.perm_flags = MSG_NEW | MSG_UNREAD;
494         flags.tmp_flags = 0;
495
496         msginfo = rssyl_parse_feed_item_to_msginfo(file, flags, TRUE, TRUE, item);
497
498         if( msginfo )
499                 msginfo->msgnum = num;
500
501         g_free(file);
502
503         return msginfo;
504 }
505
506 static gint rssyl_add_msgs(Folder *folder, FolderItem *dest, GSList *file_list,
507                 GHashTable *relation)
508 {
509         gchar *destfile;
510         GSList *cur;
511         MsgFileInfo *fileinfo;
512
513         g_return_val_if_fail(dest != NULL, -1);
514         g_return_val_if_fail(file_list != NULL, -1);
515
516         if( dest->last_num < 0 ) {
517                 rssyl_get_last_num(folder, dest);
518                 if( dest->last_num < 0 ) return -1;
519         }
520
521         for( cur = file_list; cur != NULL; cur = cur->next ) {
522                 fileinfo = (MsgFileInfo *)cur->data;
523
524                 destfile = rssyl_get_new_msg_filename(dest);
525                 g_return_val_if_fail(destfile != NULL, -1);
526
527 #ifdef G_OS_UNIX
528                 if( link(fileinfo->file, destfile) < 0 )
529 #endif
530                         if( copy_file(fileinfo->file, destfile, TRUE) < 0 ) {
531                                 g_warning("can't copy message %s to %s\n", fileinfo->file, destfile);
532                                 g_free(destfile);
533                                 return -1;
534                         }
535
536                 if( relation != NULL )
537                         g_hash_table_insert(relation, fileinfo,
538                                         GINT_TO_POINTER(dest->last_num + 1) );
539                 g_free(destfile);
540                 dest->last_num++;
541         }
542
543         return dest->last_num;
544 }
545
546 static gint rssyl_add_msg(Folder *folder, FolderItem *dest, const gchar *file,
547                 MsgFlags *flags)
548 {
549         gint ret;
550         GSList file_list;
551         MsgFileInfo fileinfo;
552
553         g_return_val_if_fail(file != NULL, -1);
554
555         fileinfo.msginfo = NULL;
556         fileinfo.file = (gchar *)file;
557         fileinfo.flags = flags;
558         file_list.data = &fileinfo;
559         file_list.next = NULL;
560
561         ret = rssyl_add_msgs(folder, dest, &file_list, NULL);
562         return ret;
563 }
564
565 static gint rssyl_dummy_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *info)
566 {
567         if (info->folder == NULL || info->folder->folder != dest->folder) {
568                 return -1;
569         }
570         if (info->folder && info->folder->name && dest->name
571         &&  !strcmp(info->folder->name, dest->name)) {
572                 /* this is a folder move */
573                 gchar *file = procmsg_get_message_file(info);
574                 gchar *tmp = g_strdup_printf("%s.tmp", file);
575                 copy_file(file, tmp, TRUE);
576                 g_free(file);
577                 g_free(tmp);
578                 return info->msgnum;
579         } else {
580                 return -1;
581         }
582 }
583 static gint rssyl_remove_msg(Folder *folder, FolderItem *item, gint num)
584 {
585         gboolean need_scan = FALSE;
586         gchar *file, *tmp;
587
588         g_return_val_if_fail(item != NULL, -1);
589
590         file = rssyl_fetch_msg(folder, item, num);
591         g_return_val_if_fail(file != NULL, -1);
592
593         need_scan = rssyl_scan_required(folder, item);
594
595         /* are we doing a folder move ? */
596         tmp = g_strdup_printf("%s.tmp", file);
597         if (is_file_exist(tmp)) {
598                 claws_unlink(tmp);
599                 g_free(tmp);
600                 g_free(file);
601                 return 0;
602         }
603         g_free(tmp);
604         if( claws_unlink(file) < 0 ) {
605                 FILE_OP_ERROR(file, "unlink");
606                 g_free(file);
607                 return -1;
608         }
609
610         if( !need_scan )
611                 item->mtime = time(NULL);
612
613         g_free(file);
614         return 0;
615 }
616
617 static gboolean rssyl_subscribe_uri(Folder *folder, const gchar *uri)
618 {
619         if (folder->klass != rssyl_folder_get_class())
620                 return FALSE;
621         
622         if( rssyl_subscribe_new_feed(
623                                 FOLDER_ITEM(folder->node->data), uri, FALSE) != NULL )
624                 return TRUE;
625         return FALSE;
626 }
627
628 /************************************************************************/
629
630 FolderClass *rssyl_folder_get_class()
631 {
632         if( rssyl_class.idstr == NULL ) {
633                 rssyl_class.type = F_UNKNOWN;
634                 rssyl_class.idstr = "rssyl";
635                 rssyl_class.uistr = "RSSyl";
636
637                 /* Folder functions */
638                 rssyl_class.new_folder = rssyl_new_folder;
639                 rssyl_class.destroy_folder = rssyl_destroy_folder;
640                 rssyl_class.set_xml = folder_set_xml;
641                 rssyl_class.get_xml = folder_get_xml;
642                 rssyl_class.scan_tree = rssyl_scan_tree;
643                 rssyl_class.create_tree = rssyl_create_tree;
644
645                 /* FolderItem functions */
646                 rssyl_class.item_new = rssyl_item_new;
647                 rssyl_class.item_destroy = rssyl_item_destroy;
648                 rssyl_class.item_get_path = rssyl_item_get_path;
649                 rssyl_class.create_folder = rssyl_create_folder;
650                 rssyl_class.rename_folder = rssyl_rename_folder;
651                 rssyl_class.remove_folder = rssyl_remove_folder;
652                 rssyl_class.get_num_list = rssyl_get_num_list;
653                 rssyl_class.scan_required = rssyl_scan_required;
654
655                 /* Message functions */
656                 rssyl_class.get_msginfo = rssyl_get_msginfo;
657                 rssyl_class.fetch_msg = rssyl_fetch_msg;
658                 rssyl_class.copy_msg = rssyl_dummy_copy_msg;
659                 rssyl_class.add_msg = rssyl_add_msg;
660                 rssyl_class.add_msgs = rssyl_add_msgs;
661                 rssyl_class.remove_msg = rssyl_remove_msg;
662                 rssyl_class.remove_msgs = NULL;
663 //              rssyl_class.change_flags = rssyl_change_flags;
664                 rssyl_class.change_flags = NULL;
665                 rssyl_class.subscribe = rssyl_subscribe_uri;
666                 debug_print("RSSyl: registered folderclass\n");
667         }
668
669         return &rssyl_class;
670 }