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