Fix superfluous semicolons from 543c7d3.
[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->auth = NULL;
44         feed->title = NULL;
45         feed->description = NULL;
46         feed->language = NULL;
47         feed->author = NULL;
48         feed->generator = NULL;
49         feed->link = NULL;
50         feed->items = NULL;
51
52         feed->fetcherr = NULL;
53         feed->cookies_path = NULL;
54
55         feed->ssl_verify_peer = TRUE;
56         feed->cacert_file = NULL;
57
58         return feed;
59 }
60
61 static void _free_items(gpointer item, gpointer nada)
62 {
63         feed_item_free(item);
64 }
65
66 static void _free_auth(Feed *feed)
67 {
68         if (feed == NULL)
69                 return;
70
71         if (feed->auth != NULL) {
72                 if (feed->auth->username != NULL)
73                         g_free(feed->auth->username);
74                 if (feed->auth->password != NULL)
75                         g_free(feed->auth->password);
76                 g_free(feed->auth);
77                 feed->auth = NULL;
78         }
79 }
80
81 void feed_free(Feed *feed)
82 {
83         if( feed == NULL )
84                 return; /* Return silently, without printing a glib error. */
85
86         g_free(feed->url);
87         _free_auth(feed);
88         g_free(feed->title);
89         g_free(feed->description);
90         g_free(feed->language);
91         g_free(feed->author);
92         g_free(feed->generator);
93         g_free(feed->link);
94         g_free(feed->fetcherr);
95         g_free(feed->cookies_path);
96         g_free(feed->cacert_file);
97
98         if( feed->items != NULL ) {
99                 g_slist_foreach(feed->items, _free_items, NULL);
100                 g_slist_free(feed->items);
101         }
102
103         g_free(feed);
104         feed = NULL;
105 }
106
107 void feed_free_items(Feed *feed)
108 {
109         if( feed == NULL )
110                 return;
111
112         if( feed->items != NULL ) {
113                 g_slist_foreach(feed->items, _free_items, NULL);
114                 g_slist_free(feed->items);
115                 feed->items = NULL;
116         }
117 }
118
119 /* Timeout */
120 void feed_set_timeout(Feed *feed, guint timeout)
121 {
122         g_return_if_fail(feed != NULL);
123         feed->timeout = timeout;
124 }
125
126 guint feed_get_timeout(Feed *feed)
127 {
128         g_return_val_if_fail(feed != NULL, 0);
129         return feed->timeout;
130 }
131
132 /* URL */
133 void feed_set_url(Feed *feed, gchar *url)
134 {
135         g_return_if_fail(feed != NULL);
136         g_return_if_fail(url != NULL);
137
138         if( feed->url != NULL ) {
139                 g_free(feed->url);
140                 feed->url = NULL;
141         }
142
143         feed->url = g_strdup(url);
144 }
145
146 gchar *feed_get_url(Feed *feed)
147 {
148         g_return_val_if_fail(feed != NULL, NULL);
149         return feed->url;
150 }
151
152 /* Auth */
153 void feed_set_auth(Feed *feed, FeedAuth *auth)
154 {
155         g_return_if_fail(feed != NULL);
156         g_return_if_fail(auth != NULL);
157
158         _free_auth(feed);
159         feed->auth = g_new0(FeedAuth, 1);
160         feed->auth->type = auth->type;
161         feed->auth->username = g_strdup(auth->username);
162         feed->auth->password = g_strdup(auth->password);
163 }
164
165 FeedAuth *feed_get_auth(Feed *feed)
166 {
167         g_return_val_if_fail(feed != NULL, NULL);
168         return feed->auth;
169 }
170
171 /* Title */
172 gchar *feed_get_title(Feed *feed)
173 {
174         g_return_val_if_fail(feed != NULL, NULL);
175         return feed->title;
176 }
177
178 void feed_set_title(Feed *feed, gchar *new_title)
179 {
180         g_return_if_fail(feed != NULL);
181         g_return_if_fail(new_title != NULL);
182
183         if (feed->title != NULL) {
184                 g_free(feed->title);
185                 feed->title = NULL;
186         }
187
188         feed->title = g_strdup(new_title);
189 }
190
191 /* Description */
192 gchar *feed_get_description(Feed *feed)
193 {
194         g_return_val_if_fail(feed != NULL, NULL);
195         return feed->description;
196 }
197
198 /* Language */
199 gchar *feed_get_language(Feed *feed)
200 {
201         g_return_val_if_fail(feed != NULL, NULL);
202         return feed->language;
203 }
204
205 /* Author */
206 gchar *feed_get_author(Feed *feed)
207 {
208         g_return_val_if_fail(feed != NULL, NULL);
209         return feed->author;
210 }
211
212 /* Generator */
213 gchar *feed_get_generator(Feed *feed)
214 {
215         g_return_val_if_fail(feed != NULL, NULL);
216         return feed->generator;
217 }
218
219 /* Fetch error (if not NULL, supplied by libcurl) */
220 gchar *feed_get_fetcherror(Feed *feed)
221 {
222         g_return_val_if_fail(feed != NULL, NULL);
223         return feed->fetcherr;
224 }
225
226 /* Returns number of items currently in the feed. */
227 gint feed_n_items(Feed *feed)
228 {
229         g_return_val_if_fail(feed != NULL, -1);
230
231         if( feed->items == NULL )       /* No items here. */
232                 return 0;
233
234         return g_slist_length(feed->items);
235 }
236
237 /* Returns nth item from feed. */
238 FeedItem *feed_nth_item(Feed *feed, guint n)
239 {
240         g_return_val_if_fail(feed != NULL, NULL);
241
242         return g_slist_nth_data(feed->items, n);
243 }
244
245 /* feed_update()
246  * Takes initialized feed with url set, fetches the feed from this url,
247  * updates rest of Feed struct members and returns HTTP response code
248  * we got from url's server. */
249 guint feed_update(Feed *feed, time_t last_update)
250 {
251         CURL *eh = NULL;
252         CURLcode res;
253         FeedParserCtx *feed_ctx = NULL;
254         glong response_code = 0;
255
256         g_return_val_if_fail(feed != NULL, FEED_ERR_NOFEED);
257         g_return_val_if_fail(feed->url != NULL, FEED_ERR_NOURL);
258
259         /* Init curl before anything else. */
260         eh = curl_easy_init();
261
262         g_return_val_if_fail(eh != NULL, FEED_ERR_INIT);
263
264         /* Curl initialized, create parser context now. */
265         feed_ctx = malloc( sizeof(FeedParserCtx) );
266
267         feed_ctx->parser = XML_ParserCreate(NULL);
268         feed_ctx->depth = 0;
269         feed_ctx->str = NULL;
270         feed_ctx->xhtml_str = NULL;
271         feed_ctx->feed = feed;
272         feed_ctx->location = 0;
273         feed_ctx->curitem = NULL;
274         feed_ctx->id_is_permalink = TRUE;
275
276         feed_ctx->name = NULL;
277         feed_ctx->mail = NULL;
278
279         /* Set initial expat handlers, which will take care of choosing
280          * correct parser later. */
281         feed_parser_set_expat_handlers(feed_ctx);
282
283         curl_easy_setopt(eh, CURLOPT_URL, feed->url);
284         curl_easy_setopt(eh, CURLOPT_NOPROGRESS, 1);
285 #ifdef CURLOPT_MUTE
286         curl_easy_setopt(eh, CURLOPT_MUTE, 1);
287 #endif
288         curl_easy_setopt(eh, CURLOPT_WRITEFUNCTION, feed_writefunc);
289         curl_easy_setopt(eh, CURLOPT_WRITEDATA, feed_ctx);
290         curl_easy_setopt(eh, CURLOPT_FOLLOWLOCATION, 1);
291         curl_easy_setopt(eh, CURLOPT_MAXREDIRS, 3);
292         curl_easy_setopt(eh, CURLOPT_TIMEOUT, feed->timeout);
293         curl_easy_setopt(eh, CURLOPT_NOSIGNAL, 1);
294         curl_easy_setopt(eh, CURLOPT_ENCODING, "");
295         curl_easy_setopt(eh, CURLOPT_USERAGENT, "libfeed 0.1");
296         curl_easy_setopt(eh, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
297
298         /* Use HTTP's If-Modified-Since feature, if application provided
299          * the timestamp of last update. */
300         if( last_update != -1 ) {
301                 curl_easy_setopt(eh, CURLOPT_TIMECONDITION,
302                                 CURL_TIMECOND_IFMODSINCE);
303                 curl_easy_setopt(eh, CURLOPT_TIMEVALUE, last_update);
304         }
305
306 #if LIBCURL_VERSION_NUM >= 0x070a00
307         if (feed->ssl_verify_peer == FALSE) {
308                 curl_easy_setopt(eh, CURLOPT_SSL_VERIFYPEER, 0);
309                 curl_easy_setopt(eh, CURLOPT_SSL_VERIFYHOST, 0);
310         }
311 #endif
312
313         if (feed->cacert_file != NULL)
314                 curl_easy_setopt(eh, CURLOPT_CAINFO, feed->cacert_file);
315
316         if(feed->cookies_path != NULL)
317                 curl_easy_setopt(eh, CURLOPT_COOKIEFILE, feed->cookies_path);
318
319         if (feed->auth != NULL) {
320                 switch (feed->auth->type) {
321                 case FEED_AUTH_NONE:
322                         break;
323                 case FEED_AUTH_BASIC:
324                         curl_easy_setopt(eh, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
325                         curl_easy_setopt(eh, CURLOPT_USERNAME,
326                                          feed->auth->username);
327                         curl_easy_setopt(eh, CURLOPT_PASSWORD,
328                                          feed->auth->password);
329                         break;
330                 default:
331                         response_code = FEED_ERR_UNAUTH; /* unknown auth */
332                         goto cleanup;
333                 }
334         }
335
336         res = curl_easy_perform(eh);
337         XML_Parse(feed_ctx->parser, "", 0, TRUE);
338
339         if( res != CURLE_OK ) {
340                 feed->fetcherr = g_strdup(curl_easy_strerror(res));
341                 response_code = FEED_ERR_FETCH;
342         } else
343                 curl_easy_getinfo(eh, CURLINFO_RESPONSE_CODE, &response_code);
344
345 cleanup:
346         curl_easy_cleanup(eh);
347
348         /* Cleanup, we should be done. */
349         XML_ParserFree(feed_ctx->parser);
350         g_free(feed_ctx->name);
351         g_free(feed_ctx->mail);
352         if (feed_ctx->str != NULL)
353                 g_string_free(feed_ctx->str, TRUE);
354         if (feed_ctx->xhtml_str != NULL)
355                 g_string_free(feed_ctx->xhtml_str, TRUE);
356         g_free(feed_ctx);
357
358         return response_code;
359 }
360
361 void feed_foreach_item(Feed *feed, GFunc func, gpointer data)
362 {
363         g_return_if_fail(feed != NULL);
364         g_return_if_fail(feed->items != NULL);
365
366         g_slist_foreach(feed->items, func, data);
367 }
368
369 gboolean feed_prepend_item(Feed *feed, FeedItem *item)
370 {
371         g_return_val_if_fail(feed != NULL, FALSE);
372         g_return_val_if_fail(item != NULL, FALSE);
373
374         feed->items = g_slist_prepend(feed->items, item);
375         return TRUE;
376 }
377
378 gboolean feed_append_item(Feed *feed, FeedItem *item)
379 {
380         g_return_val_if_fail(feed != NULL, FALSE);
381         g_return_val_if_fail(item != NULL, FALSE);
382
383         feed->items = g_slist_append(feed->items, item);
384         return TRUE;
385 }
386
387 gboolean feed_insert_item(Feed *feed, FeedItem *item, gint pos)
388 {
389         g_return_val_if_fail(feed != NULL, FALSE);
390         g_return_val_if_fail(item != NULL, FALSE);
391         g_return_val_if_fail(pos < 0, FALSE);
392
393         feed->items = g_slist_insert(feed->items, item, pos);
394         return TRUE;
395 }
396
397 gchar *feed_get_cookies_path(Feed *feed)
398 {
399         g_return_val_if_fail(feed != NULL, NULL);
400         return feed->cookies_path;
401 }
402
403 void feed_set_cookies_path(Feed *feed, gchar *path)
404 {
405         g_return_if_fail(feed != NULL);
406
407         if( feed->cookies_path != NULL ) {
408                 g_free(feed->cookies_path);
409                 feed->cookies_path = NULL;
410         }
411
412         feed->cookies_path = (path != NULL ? g_strdup(path) : NULL);
413 }
414
415 gboolean feed_get_ssl_verify_peer(Feed *feed)
416 {
417         g_return_val_if_fail(feed != NULL, FALSE);
418         return feed->ssl_verify_peer;
419 }
420
421 void feed_set_ssl_verify_peer(Feed *feed, gboolean ssl_verify_peer)
422 {
423         g_return_if_fail(feed != NULL);
424         feed->ssl_verify_peer = ssl_verify_peer;
425 }
426
427 gchar *feed_get_cacert_file(Feed *feed)
428 {
429         g_return_val_if_fail(feed != NULL, NULL);
430         return feed->cacert_file;
431 }
432
433 void feed_set_cacert_file(Feed *feed, gchar *path)
434 {
435         g_return_if_fail(feed != NULL);
436
437         if( feed->cacert_file != NULL ) {
438                 g_free(feed->cacert_file);
439                 feed->cacert_file = NULL;
440         }
441
442         feed->cacert_file = (path != NULL ? g_strdup(path) : NULL);
443 }