1 /*
2  * Copyright (C) 2006 Andrej Kacian <andrej@kacian.sk>
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public
15  * License along with this program; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23 
24 /* Global includes */
25 #include <glib.h>
26 #include <glib/gi18n.h>
27 #include <pthread.h>
28 
29 /* Claws Mail includes */
30 #include <common/claws.h>
31 #include <mainwindow.h>
32 #include <statusbar.h>
33 #include <alertpanel.h>
34 #include <log.h>
35 #include <prefs_common.h>
36 #include <inc.h>
37 #include <main.h>
38 
39 /* Local includes */
40 #include "libfeed/feed.h"
41 #include "rssyl.h"
42 #include "rssyl_deleted.h"
43 #include "rssyl_feed.h"
44 #include "rssyl_parse_feed.h"
45 #include "rssyl_prefs.h"
46 #include "rssyl_update_comments.h"
47 
48 /* rssyl_fetch_feed_thr() */
49 
rssyl_fetch_feed_thr(void * arg)50 static void *rssyl_fetch_feed_thr(void *arg)
51 {
52 	RFetchCtx *ctx = (RFetchCtx *)arg;
53 
54 	/* Fetch and parse the feed. */
55 	ctx->response_code = feed_update(ctx->feed, -1);
56 
57 	/* Signal main thread that we're done here. */
58 	ctx->ready = TRUE;
59 
60 	return NULL;
61 }
62 
63 /* rssyl_fetch_feed() */
rssyl_fetch_feed(RFetchCtx * ctx,RSSylVerboseFlags verbose)64 void rssyl_fetch_feed(RFetchCtx *ctx, RSSylVerboseFlags verbose)
65 {
66 #ifdef USE_PTHREAD
67 	pthread_t pt;
68 #endif
69 
70 	g_return_if_fail(ctx != NULL);
71 
72 #ifdef USE_PTHREAD
73 	if( pthread_create(&pt, NULL, rssyl_fetch_feed_thr,
74 				(void *)ctx) != 0 ) {
75 		/* Bummer, couldn't create thread. Continue non-threaded. */
76 		rssyl_fetch_feed_thr(ctx);
77 	} else {
78 		/* Thread created, let's wait until it finishes. */
79 		debug_print("RSSyl: waiting for thread to finish (timeout: %ds)\n",
80 				feed_get_timeout(ctx->feed));
81 		while( !ctx->ready ) {
82 			claws_do_idle();
83 		}
84 
85 		debug_print("RSSyl: thread finished\n");
86 		pthread_join(pt, NULL);
87 	}
88 #else
89 	debug_print("RSSyl: no pthreads available, running non-threaded fetch\n");
90 	rssyl_fetch_feed_thr(ctx);
91 #endif
92 
93 	debug_print("RSSyl: got response_code %d\n", ctx->response_code);
94 
95 	if( ctx->response_code == FEED_ERR_INIT ) {
96 		debug_print("RSSyl: libfeed reports init error from libcurl\n");
97 		ctx->error = g_strdup("Internal error");
98 	} else if( ctx->response_code == FEED_ERR_FETCH ) {
99 		debug_print("RSSyl: libfeed reports some other error from libcurl\n");
100 		ctx->error = g_strdup(ctx->feed->fetcherr);
101 	} else if( ctx->response_code == FEED_ERR_UNAUTH ) {
102 		debug_print("RSSyl: URL authorization type is unknown\n");
103 		ctx->error = g_strdup("Unknown value for URL authorization type");
104 	} else if( ctx->response_code >= 400 && ctx->response_code < 500 ) {
105 		switch( ctx->response_code ) {
106 			case 401:
107 				ctx->error = g_strdup(_("401 (Authorisation required)"));
108 				break;
109 			case 403:
110 				ctx->error = g_strdup(_("403 (Forbidden)"));
111 				break;
112 			case 404:
113 				ctx->error = g_strdup(_("404 (Not found)"));
114 				break;
115 			default:
116 				ctx->error = g_strdup_printf(_("Error %d"), ctx->response_code);
117 				break;
118 		}
119 	}
120 
121 	/* Here we handle "imperfect" conditions. If requested, we also
122 	 * display error dialogs for user. We always log the error. */
123 	if( ctx->error != NULL ) {
124 		/* libcurl wasn't happy */
125 		debug_print("RSSyl: Error: %s\n", ctx->error);
126 		if( verbose & RSSYL_SHOW_ERRORS) {
127 			gchar *msg = g_markup_printf_escaped(
128 					(const char *) C_("First parameter is URL, second is error text",
129 						"Error fetching feed at\n<b>%s</b>:\n\n%s"),
130 					feed_get_url(ctx->feed), ctx->error);
131 			alertpanel_error("%s", msg);
132 			g_free(msg);
133 		}
134 
135 		log_error(LOG_PROTOCOL, RSSYL_LOG_ERROR_FETCH, ctx->feed->url, ctx->error);
136 
137 		ctx->success = FALSE;
138 	} else {
139 		if( ctx->feed == NULL || ctx->response_code == FEED_ERR_NOFEED) {
140 			if( verbose & RSSYL_SHOW_ERRORS) {
141 				gchar *msg = g_markup_printf_escaped(
142 						(const char *) _("No valid feed found at\n<b>%s</b>"),
143 						feed_get_url(ctx->feed));
144 				alertpanel_error("%s", msg);
145 				g_free(msg);
146 			}
147 
148 			log_error(LOG_PROTOCOL, RSSYL_LOG_ERROR_NOFEED,
149 					feed_get_url(ctx->feed));
150 
151 			ctx->success = FALSE;
152 		} else if (feed_get_title(ctx->feed) == NULL) {
153 			/* We shouldn't do this, since a title is mandatory. */
154 			feed_set_title(ctx->feed, _("Untitled feed"));
155 			log_print(LOG_PROTOCOL,
156 					_("RSSyl: Possibly invalid feed without title at %s.\n"),
157 					feed_get_url(ctx->feed));
158 		}
159 	}
160 }
161 
rssyl_prep_fetchctx_from_item(RFolderItem * ritem)162 RFetchCtx *rssyl_prep_fetchctx_from_item(RFolderItem *ritem)
163 {
164 	RFetchCtx *ctx = NULL;
165 
166 	g_return_val_if_fail(ritem != NULL, NULL);
167 
168 	ctx = g_new0(RFetchCtx, 1);
169 	ctx->feed = feed_new(ritem->url);
170 	ctx->error = NULL;
171 	ctx->success = TRUE;
172 	ctx->ready = FALSE;
173 
174 	if (ritem->auth->type != FEED_AUTH_NONE)
175 		ritem->auth->password = rssyl_passwd_get(ritem);
176 
177 	feed_set_timeout(ctx->feed, prefs_common_get_prefs()->io_timeout_secs);
178 	feed_set_cookies_path(ctx->feed, rssyl_prefs_get()->cookies_path);
179 	feed_set_ssl_verify_peer(ctx->feed, ritem->ssl_verify_peer);
180 	feed_set_auth(ctx->feed, ritem->auth);
181 #ifdef G_OS_WIN32
182 	if (!g_ascii_strncasecmp(ritem->url, "https", 5)) {
183 		feed_set_cacert_file(ctx->feed, claws_ssl_get_cert_file());
184 		debug_print("RSSyl: using cert file '%s'\n", feed_get_cacert_file(ctx->feed));
185 	}
186 #endif
187 
188 	return ctx;
189 }
190 
rssyl_prep_fetchctx_from_url(gchar * url)191 RFetchCtx *rssyl_prep_fetchctx_from_url(gchar *url)
192 {
193 	RFetchCtx *ctx = NULL;
194 
195 	g_return_val_if_fail(url != NULL, NULL);
196 
197 	ctx = g_new0(RFetchCtx, 1);
198 	ctx->feed = feed_new(url);
199 	ctx->error = NULL;
200 	ctx->success = TRUE;
201 	ctx->ready = FALSE;
202 
203 	feed_set_timeout(ctx->feed, prefs_common_get_prefs()->io_timeout_secs);
204 	feed_set_cookies_path(ctx->feed, rssyl_prefs_get()->cookies_path);
205 	feed_set_ssl_verify_peer(ctx->feed, rssyl_prefs_get()->ssl_verify_peer);
206 #ifdef G_OS_WIN32
207 	if (!g_ascii_strncasecmp(url, "https", 5)) {
208 		feed_set_cacert_file(ctx->feed, claws_ssl_get_cert_file());
209 		debug_print("RSSyl: using cert file '%s'\n", feed_get_cacert_file(ctx->feed));
210 	}
211 #endif
212 
213 	return ctx;
214 }
215 
216 /* rssyl_update_feed() */
217 
rssyl_update_feed(RFolderItem * ritem,RSSylVerboseFlags verbose)218 gboolean rssyl_update_feed(RFolderItem *ritem, RSSylVerboseFlags verbose)
219 {
220 	RFetchCtx *ctx = NULL;
221 	MainWindow *mainwin = mainwindow_get_mainwindow();
222 	gchar *msg = NULL;
223 	gboolean success = FALSE;
224 
225 	g_return_val_if_fail(ritem != NULL, FALSE);
226 	g_return_val_if_fail(ritem->url != NULL, FALSE);
227 
228 	debug_print("RSSyl: starting to update '%s' (%s)\n",
229 			ritem->item.name, ritem->url);
230 
231 	log_print(LOG_PROTOCOL, RSSYL_LOG_UPDATING, ritem->url);
232 
233 	msg = g_strdup_printf(_("Updating feed '%s'..."), ritem->item.name);
234 	STATUSBAR_PUSH(mainwin, msg);
235 	g_free(msg);
236 
237 	GTK_EVENTS_FLUSH();
238 
239 	/* Prepare context for fetching the feed file */
240 	ctx = rssyl_prep_fetchctx_from_item(ritem);
241 	g_return_val_if_fail(ctx != NULL, FALSE);
242 
243 	/* Fetch the feed file */
244 	rssyl_fetch_feed(ctx, verbose);
245 
246 	if (ritem->auth != NULL && ritem->auth->password != NULL) {
247 		memset(ritem->auth->password, 0, strlen(ritem->auth->password));
248 		g_free(ritem->auth->password);
249 	}
250 
251 	debug_print("RSSyl: fetch done; success == %s\n",
252 			ctx->success ? "TRUE" : "FALSE");
253 
254 	if (!ctx->success) {
255 		feed_free(ctx->feed);
256 		g_free(ctx->error);
257 		g_free(ctx);
258 		STATUSBAR_POP(mainwin);
259 		return FALSE;
260 	}
261 
262 	rssyl_deleted_update(ritem);
263 
264 	debug_print("RSSyl: STARTING TO PARSE FEED\n");
265   if( ctx->success && !(ctx->success = rssyl_parse_feed(ritem, ctx->feed)) ) {
266 		/* both libcurl and libfeed were happy, but we weren't */
267 		debug_print("RSSyl: Error processing feed\n");
268 		if( verbose & RSSYL_SHOW_ERRORS ) {
269 			gchar *msg = g_markup_printf_escaped(
270 					(const char *) _("Couldn't process feed at\n<b>%s</b>\n\n"
271 						"Please contact developers, this should not happen."),
272 					feed_get_url(ctx->feed));
273 			alertpanel_error("%s", msg);
274 			g_free(msg);
275 		}
276 
277 		log_error(LOG_PROTOCOL, RSSYL_LOG_ERROR_PROC, ctx->feed->url);
278 	}
279 
280 	debug_print("RSSyl: FEED PARSED\n");
281 
282 	STATUSBAR_POP(mainwin);
283 
284 	if( claws_is_exiting() ) {
285 		feed_free(ctx->feed);
286 		g_free(ctx->error);
287 		g_free(ctx);
288 		return FALSE;
289 	}
290 
291 	if( ritem->fetch_comments )
292 		rssyl_update_comments(ritem);
293 
294 	/* Prune our deleted items list of items which are no longer in
295 	 * upstream feed. */
296 	rssyl_deleted_expire(ritem, ctx->feed);
297 	rssyl_deleted_store(ritem);
298 	rssyl_deleted_free(ritem);
299 
300 	/* Clean up. */
301 	success = ctx->success;
302 	feed_free(ctx->feed);
303 	g_free(ctx->error);
304 	g_free(ctx);
305 
306 	return success;
307 }
308 
rssyl_update_recursively_func(GNode * node,gpointer data)309 static gboolean rssyl_update_recursively_func(GNode *node, gpointer data)
310 {
311 	FolderItem *item;
312 	RFolderItem *ritem;
313 
314 	g_return_val_if_fail(node->data != NULL, FALSE);
315 
316 	item = FOLDER_ITEM(node->data);
317 	ritem = (RFolderItem *)item;
318 
319 	if( ritem->url != NULL ) {
320 		debug_print("RSSyl: Updating feed '%s'\n", item->name);
321 		rssyl_update_feed(ritem, 0);
322 	} else
323 		debug_print("RSSyl: Updating in folder '%s'\n", item->name);
324 
325 	return FALSE;
326 }
327 
rssyl_update_recursively(FolderItem * item)328 void rssyl_update_recursively(FolderItem *item)
329 {
330 	g_return_if_fail(item != NULL);
331 	g_return_if_fail(item->folder != NULL);
332 
333 	if( item->folder->klass != rssyl_folder_get_class() )
334 		return;
335 
336 	debug_print("Recursively updating '%s'\n", item->name);
337 
338 	g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
339 			rssyl_update_recursively_func, NULL);
340 }
341 
rssyl_update_all_func(FolderItem * item,gpointer data)342 void rssyl_update_all_func(FolderItem *item, gpointer data)
343 {
344 	/* Only try to refresh our feed folders */
345 	if( !IS_RSSYL_FOLDER_ITEM(item) )
346 		return;
347 
348 	if( folder_item_parent(item) == NULL )
349 		rssyl_update_recursively(item);
350 }
351 
rssyl_update_all_feeds(void)352 void rssyl_update_all_feeds(void)
353 {
354 	if (prefs_common_get_prefs()->work_offline &&
355 			!inc_offline_should_override(TRUE,
356 				_("Claws Mail needs network access in order to update your feeds.")) ) {
357 		return;
358 	}
359 
360 	folder_func_to_all_folders((FolderItemFunc)rssyl_update_all_func, NULL);
361 }
362