RSSyl: Convert relative URLs in Atom entries to absolute URLs, using feed's <link...
[claws.git] / src / plugins / rssyl / libfeed / feed.c
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 #define __USE_GNU
21
22 #include <stdlib.h>
23 #include <glib.h>
24 #include <curl/curl.h>
25 #include <expat.h>
26
27 #include "feed.h"
28 #include "parser.h"
29
30 /* feed_new()
31  * Initializes new Feed struct, setting its url and a default timeout. */
32 Feed *feed_new(gchar *url)
33 {
34         Feed *feed = NULL;
35
36         g_return_val_if_fail(url != NULL, NULL);
37
38         feed = malloc( sizeof(Feed) );
39         g_return_val_if_fail(feed != NULL, NULL);
40
41         feed->timeout = FEED_DEFAULT_TIMEOUT;
42         feed->url = g_strdup(url);
43         feed->title = NULL;
44         feed->description = NULL;
45         feed->language = NULL;
46         feed->author = NULL;
47         feed->generator = NULL;
48         feed->link = NULL;
49         feed->items = NULL;
50
51         feed->fetcherr = NULL;
52         feed->cookies_path = NULL;
53
54         return feed;
55 }
56
57 static void _free_items(gpointer item, gpointer nada)
58 {
59         feed_item_free(item);
60 }
61
62 void feed_free(Feed *feed)
63 {
64         if( feed == NULL )
65                 return; /* Return silently, without printing a glib error. */
66
67         g_free(feed->url);
68         g_free(feed->title);
69         g_free(feed->description);
70         g_free(feed->language);
71         g_free(feed->author);
72         g_free(feed->generator);
73         g_free(feed->link);
74         g_free(feed->fetcherr);
75         g_free(feed->cookies_path);
76
77         if( feed->items != NULL ) {
78                 g_slist_foreach(feed->items, _free_items, NULL);
79                 g_slist_free(feed->items);
80         }
81
82         g_free(feed);
83         feed = NULL;
84 }
85
86 void feed_free_items(Feed *feed)
87 {
88         if( feed == NULL )
89                 return;
90
91         if( feed->items != NULL ) {
92                 g_slist_foreach(feed->items, _free_items, NULL);
93                 g_slist_free(feed->items);
94                 feed->items = NULL;
95         }
96 }
97
98 /* Timeout */
99 void feed_set_timeout(Feed *feed, guint timeout)
100 {
101         g_return_if_fail(feed != NULL);
102         feed->timeout = timeout;
103 }
104
105 guint feed_get_timeout(Feed *feed)
106 {
107         g_return_val_if_fail(feed != NULL, 0);
108         return feed->timeout;
109 }
110
111 /* URL */
112 void feed_set_url(Feed *feed, gchar *url)
113 {
114         g_return_if_fail(feed != NULL);
115         g_return_if_fail(url != NULL);
116
117         if( feed->url != NULL ) {
118                 g_free(feed->url);
119                 feed->url = NULL;
120         }
121
122         feed->url = g_strdup(url);
123 }
124
125 gchar *feed_get_url(Feed *feed)
126 {
127         g_return_val_if_fail(feed != NULL, NULL);
128         return feed->url;
129 }
130
131 /* Title */
132 gchar *feed_get_title(Feed *feed)
133 {
134         g_return_val_if_fail(feed != NULL, NULL);
135         return feed->title;
136 }
137
138 void feed_set_title(Feed *feed, gchar *new_title)
139 {
140         g_return_if_fail(feed != NULL);
141         g_return_if_fail(new_title != NULL);
142
143         if (feed->title != NULL) {
144                 g_free(feed->title);
145                 feed->title = NULL;
146         }
147
148         feed->title = g_strdup(new_title);
149 }
150
151 /* Description */
152 gchar *feed_get_description(Feed *feed)
153 {
154         g_return_val_if_fail(feed != NULL, NULL);
155         return feed->description;
156 }
157
158 /* Language */
159 gchar *feed_get_language(Feed *feed)
160 {
161         g_return_val_if_fail(feed != NULL, NULL);
162         return feed->language;
163 }
164
165 /* Author */
166 gchar *feed_get_author(Feed *feed)
167 {
168         g_return_val_if_fail(feed != NULL, NULL);
169         return feed->author;
170 }
171
172 /* Generator */
173 gchar *feed_get_generator(Feed *feed)
174 {
175         g_return_val_if_fail(feed != NULL, NULL);
176         return feed->generator;
177 }
178
179 /* Fetch error (if not NULL, supplied by libcurl) */
180 gchar *feed_get_fetcherror(Feed *feed)
181 {
182         g_return_val_if_fail(feed != NULL, NULL);
183         return feed->fetcherr;
184 }
185
186 /* Returns number of items currently in the feed. */
187 gint feed_n_items(Feed *feed)
188 {
189         g_return_val_if_fail(feed != NULL, -1);
190
191         if( feed->items == NULL )       /* No items here. */
192                 return 0;
193
194         return g_slist_length(feed->items);
195 }
196
197 /* Returns nth item from feed. */
198 FeedItem *feed_nth_item(Feed *feed, guint n)
199 {
200         g_return_val_if_fail(feed != NULL, NULL);
201
202         return g_slist_nth_data(feed->items, n);
203 }
204
205 /* feed_update()
206  * Takes initialized feed with url set, fetches the feed from this url,
207  * updates rest of Feed struct members and returns HTTP response code
208  * we got from url's server. */
209 guint feed_update(Feed *feed, time_t last_update)
210 {
211         CURL *eh = NULL;
212         CURLcode res;
213         FeedParserCtx *feed_ctx = NULL;
214         glong response_code = 0;
215
216         g_return_val_if_fail(feed != NULL, FEED_ERR_NOFEED);
217         g_return_val_if_fail(feed->url != NULL, FEED_ERR_NOURL);
218
219         /* Init curl before anything else. */
220         eh = curl_easy_init();
221
222         g_return_val_if_fail(eh != NULL, FEED_ERR_INIT);
223
224         /* Curl initialized, create parser context now. */
225         feed_ctx = malloc( sizeof(FeedParserCtx) );
226
227         feed_ctx->parser = XML_ParserCreate(NULL);
228         feed_ctx->depth = 0;
229         feed_ctx->str = NULL;
230         feed_ctx->feed = feed;
231         feed_ctx->location = 0;
232         feed_ctx->curitem = NULL;
233         feed_ctx->id_is_permalink = TRUE;
234
235         feed_ctx->name = NULL;
236         feed_ctx->mail = NULL;
237
238         /* Set initial expat handlers, which will take care of choosing
239          * correct parser later. */
240         feed_parser_set_expat_handlers(feed_ctx);
241
242         curl_easy_setopt(eh, CURLOPT_URL, feed->url);
243         curl_easy_setopt(eh, CURLOPT_NOPROGRESS, 1);
244 #ifdef CURLOPT_MUTE
245         curl_easy_setopt(eh, CURLOPT_MUTE, 1);
246 #endif
247         curl_easy_setopt(eh, CURLOPT_WRITEFUNCTION, feed_writefunc);
248         curl_easy_setopt(eh, CURLOPT_WRITEDATA, feed_ctx);
249         curl_easy_setopt(eh, CURLOPT_FOLLOWLOCATION, 1);
250         curl_easy_setopt(eh, CURLOPT_MAXREDIRS, 3);
251         curl_easy_setopt(eh, CURLOPT_TIMEOUT, feed->timeout);
252         curl_easy_setopt(eh, CURLOPT_NOSIGNAL, 1);
253         curl_easy_setopt(eh, CURLOPT_ENCODING, "");
254         curl_easy_setopt(eh, CURLOPT_USERAGENT, "libfeed 0.1");
255         curl_easy_setopt(eh, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
256
257         /* Use HTTP's If-Modified-Since feature, if application provided
258          * the timestamp of last update. */
259         if( last_update != -1 ) {
260                 curl_easy_setopt(eh, CURLOPT_TIMECONDITION,
261                                 CURL_TIMECOND_IFMODSINCE);
262                 curl_easy_setopt(eh, CURLOPT_TIMEVALUE, last_update);
263         }
264
265 #if LIBCURL_VERSION_NUM >= 0x070a00
266         if (feed->ssl_verify_peer == FALSE) {
267                 curl_easy_setopt(eh, CURLOPT_SSL_VERIFYPEER, 0);
268                 curl_easy_setopt(eh, CURLOPT_SSL_VERIFYHOST, 0);
269         }
270 #endif
271
272         if(feed->cookies_path != NULL)
273                 curl_easy_setopt(eh, CURLOPT_COOKIEFILE, feed->cookies_path);
274
275         res = curl_easy_perform(eh);
276         XML_Parse(feed_ctx->parser, "", 0, TRUE);
277
278         if( res != CURLE_OK ) {
279                 feed->fetcherr = g_strdup(curl_easy_strerror(res));
280                 response_code = FEED_ERR_FETCH;
281         } else
282                 curl_easy_getinfo(eh, CURLINFO_RESPONSE_CODE, &response_code);
283
284         curl_easy_cleanup(eh);
285
286         /* Cleanup, we should be done. */
287         XML_ParserFree(feed_ctx->parser);
288         g_free(feed_ctx->name);
289         g_free(feed_ctx->mail);
290         g_free(feed_ctx);
291
292         return response_code;
293 }
294
295 void feed_foreach_item(Feed *feed, GFunc func, gpointer data)
296 {
297         g_return_if_fail(feed != NULL);
298         g_return_if_fail(feed->items != NULL);
299
300         g_slist_foreach(feed->items, func, data);
301 }
302
303 gboolean feed_prepend_item(Feed *feed, FeedItem *item)
304 {
305         g_return_val_if_fail(feed != NULL, FALSE);
306         g_return_val_if_fail(item != NULL, FALSE);
307
308         feed->items = g_slist_prepend(feed->items, item);
309         return TRUE;
310 }
311
312 gboolean feed_append_item(Feed *feed, FeedItem *item)
313 {
314         g_return_val_if_fail(feed != NULL, FALSE);
315         g_return_val_if_fail(item != NULL, FALSE);
316
317         feed->items = g_slist_append(feed->items, item);
318         return TRUE;
319 }
320
321 gboolean feed_insert_item(Feed *feed, FeedItem *item, gint pos)
322 {
323         g_return_val_if_fail(feed != NULL, FALSE);
324         g_return_val_if_fail(item != NULL, FALSE);
325         g_return_val_if_fail(pos < 0, FALSE);
326
327         feed->items = g_slist_insert(feed->items, item, pos);
328         return TRUE;
329 }
330
331 gchar *feed_get_cookies_path(Feed *feed)
332 {
333         g_return_val_if_fail(feed != NULL, NULL);
334         return feed->cookies_path;
335 }
336
337 void feed_set_cookies_path(Feed *feed, gchar *path)
338 {
339         g_return_if_fail(feed != NULL);
340
341         if( feed->cookies_path != NULL ) {
342                 g_free(feed->cookies_path);
343                 feed->cookies_path = NULL;
344         }
345
346         feed->cookies_path = (path != NULL ? g_strdup(path) : NULL);
347 }
348
349 gboolean feed_get_ssl_verify_peer(Feed *feed)
350 {
351         g_return_val_if_fail(feed != NULL, FALSE);
352         return feed->ssl_verify_peer;
353 }
354
355 void feed_set_ssl_verify_peer(Feed *feed, gboolean ssl_verify_peer)
356 {
357         g_return_if_fail(feed != NULL);
358         feed->ssl_verify_peer = ssl_verify_peer;
359 }