[ecore] merged svn latest code (svn54830)
[profile/ivi/ecore.git] / src / lib / ecore_con / ecore_con_url.c
1 /*
2  * For info on how to use libcurl, see:
3  * http://curl.haxx.se/libcurl/c/libcurl-tutorial.html
4  */
5
6 /*
7  * Brief usage:
8  * 1. Create an Ecore_Con_Url object
9  * 2. Register to receive the ECORE_CON_EVENT_URL_COMPLETE event
10  *    (and optionally the ECORE_CON_EVENT_URL_DATA event to receive
11  *    the response, e.g. for HTTP/FTP downloads)
12  * 3. Set the URL with ecore_con_url_url_set(...);
13  * 4. Perform the operation with ecore_con_url_send(...);
14  *
15  * Note that it is good to reuse Ecore_Con_Url objects wherever possible, but
16  * bear in mind that each one can only perform one operation at a time.
17  * You need to wait for the ECORE_CON_EVENT_URL_COMPLETE event before re-using
18  * or destroying the object.
19  *
20  * Example Usage 1 (HTTP GET):
21  *   ecore_con_url_url_set(url_con, "http://www.google.com");
22  *   ecore_con_url_send(url_con, NULL, 0, NULL);
23  *
24  * Example usage 2 (HTTP POST):
25  *   ecore_con_url_url_set(url_con, "http://www.example.com/post_handler.cgi");
26  *   ecore_con_url_send(url_con, data, data_length, "multipart/form-data");
27  *
28  * Example Usage 3 (FTP download):
29  *   ecore_con_url_url_set(url_con, "ftp://ftp.example.com/pub/myfile");
30  *   ecore_con_url_send(url_con, NULL, 0, NULL);
31  *
32  * Example Usage 4 (FTP upload as ftp://ftp.example.com/file):
33  *   ecore_con_url_url_set(url_con, "ftp://ftp.example.com");
34  *   ecore_con_url_ftp_upload(url_con, "/tmp/file", "user", "pass", NULL);
35  *
36  * Example Usage 5 (FTP upload as ftp://ftp.example.com/dir/file):
37  *   ecore_con_url_url_set(url_con, "ftp://ftp.example.com");
38  *   ecore_con_url_ftp_upload(url_con, "/tmp/file", "user", "pass","dir");
39  *
40  * FIXME: Support more CURL features: Authentication, Progress callbacks and more...
41  */
42
43 #ifdef HAVE_CONFIG_H
44 # include <config.h>
45 #endif
46
47 #include <string.h>
48 #include <errno.h>
49 #include <sys/stat.h>
50 #include <sys/types.h>
51 #include <unistd.h>
52
53 #ifdef HAVE_WS2TCPIP_H
54 # include <ws2tcpip.h>
55 #endif
56
57 #include "Ecore.h"
58 #include "ecore_private.h"
59 #include "Ecore_Con.h"
60 #include "ecore_con_private.h"
61
62 int ECORE_CON_EVENT_URL_DATA = 0;
63 int ECORE_CON_EVENT_URL_COMPLETE = 0;
64 int ECORE_CON_EVENT_URL_PROGRESS = 0;
65
66 #ifdef HAVE_CURL
67 static Eina_Bool _ecore_con_url_fd_handler(void *data,
68                                            Ecore_Fd_Handler *fd_handler);
69 static Eina_Bool _ecore_con_url_perform(Ecore_Con_Url *url_con);
70 static size_t    _ecore_con_url_header_cb(void *ptr, size_t size, size_t nitems,
71                                           void *stream);
72 static size_t    _ecore_con_url_data_cb(void *buffer,
73                                         size_t size,
74                                         size_t nitems,
75                                         void *userp);
76 static int       _ecore_con_url_progress_cb(void *clientp, double dltotal,
77                                             double dlnow, double ultotal,
78                                             double ulnow);
79 static size_t    _ecore_con_url_read_cb(void *ptr, size_t size, size_t nitems,
80                                         void *stream);
81 static void      _ecore_con_event_url_free(void *data __UNUSED__, void *ev);
82 static int       _ecore_con_url_process_completed_jobs(
83    Ecore_Con_Url *url_con_to_match);
84 static Eina_Bool _ecore_con_url_idler_handler(void *data);
85
86 static Ecore_Idler *_fd_idler_handler = NULL;
87 static Eina_List *_url_con_list = NULL;
88 static CURLM *_curlm = NULL;
89 static fd_set _current_fd_set;
90 static int _init_count = 0;
91 static Ecore_Timer *_curl_timeout = NULL;
92
93 typedef struct _Ecore_Con_Url_Event Ecore_Con_Url_Event;
94 struct _Ecore_Con_Url_Event
95 {
96    int type;
97    void *ev;
98 };
99
100 static Eina_Bool
101 _url_complete_idler_cb(void *data)
102 {
103    Ecore_Con_Url_Event *lev;
104
105    lev = data;
106    ecore_event_add(lev->type, lev->ev, _ecore_con_event_url_free, NULL);
107    free(lev);
108
109    return ECORE_CALLBACK_CANCEL;
110 }
111
112 static void
113 _url_complete_push_event(int type, void *ev)
114 {
115    Ecore_Con_Url_Event *lev;
116
117    lev = malloc(sizeof(Ecore_Con_Url_Event));
118    lev->type = type;
119    lev->ev = ev;
120
121    ecore_idler_add(_url_complete_idler_cb, lev);
122 }
123
124 #endif
125
126 /**
127  * @addtogroup Ecore_Con_Url_Group Ecore URL Connection Functions
128  *
129  * Utility functions that set up, use and shut down the Ecore URL
130  * Connection library.
131  *
132  * @todo write detailed description of Ecore_Con_Url
133  *
134  * @{
135  */
136
137 /**
138  * Initialises the Ecore_Con_Url library.
139  * @return Number of times the library has been initialised without being
140  *          shut down.
141  */
142 EAPI int
143 ecore_con_url_init(void)
144 {
145 #ifdef HAVE_CURL
146    _init_count++;
147
148    if (_init_count > 1)
149       return _init_count;
150
151    if (!ECORE_CON_EVENT_URL_DATA)
152      {
153         ECORE_CON_EVENT_URL_DATA = ecore_event_type_new();
154         ECORE_CON_EVENT_URL_COMPLETE = ecore_event_type_new();
155         ECORE_CON_EVENT_URL_PROGRESS = ecore_event_type_new();
156      }
157
158    if (!_curlm)
159      {
160         long ms;
161
162         FD_ZERO(&_current_fd_set);
163         if (curl_global_init(CURL_GLOBAL_NOTHING))
164           {
165              while (_url_con_list)
166                 ecore_con_url_free(eina_list_data_get(_url_con_list));
167              return 0;
168           }
169
170         _curlm = curl_multi_init();
171         if (!_curlm)
172           {
173              while (_url_con_list)
174                 ecore_con_url_free(eina_list_data_get(_url_con_list));
175
176              _init_count--;
177              return 0;
178           }
179
180         curl_multi_timeout(_curlm, &ms);
181         if (ms <= 0)
182            ms = 1000;
183
184         _curl_timeout =
185            ecore_timer_add((double)ms / 1000, _ecore_con_url_idler_handler,
186                            (void *)0xACE);
187         ecore_timer_freeze(_curl_timeout);
188      }
189
190    return 1;
191 #else
192    return 0;
193 #endif
194 }
195
196 /**
197  * Shuts down the Ecore_Con_Url library.
198  * @return  Number of calls that still uses Ecore_Con_Url
199  */
200 EAPI int
201 ecore_con_url_shutdown(void)
202 {
203 #ifdef HAVE_CURL
204    if (!_init_count)
205       return 0;
206
207    _init_count--;
208
209    if (_init_count != 0)
210       return _init_count;
211
212    if (_fd_idler_handler)
213       ecore_idler_del(_fd_idler_handler);
214
215    _fd_idler_handler = NULL;
216
217    if (_curl_timeout)
218       ecore_timer_del(_curl_timeout);
219
220    _curl_timeout = NULL;
221
222    while (_url_con_list)
223       ecore_con_url_free(eina_list_data_get(_url_con_list));
224
225    if (_curlm)
226      {
227         curl_multi_cleanup(_curlm);
228         _curlm = NULL;
229      }
230
231    curl_global_cleanup();
232 #endif
233    return 1;
234 }
235
236 /**
237  * Creates and initializes a new Ecore_Con_Url connection object.
238  *
239  * Creates and initializes a new Ecore_Con_Url connection object that can be
240  * uesd for sending requests.
241  *
242  * @param url URL that will receive requests. Can be changed using
243  *            ecore_con_url_url_set.
244  *
245  * @return NULL on error, a new Ecore_Con_Url on success.
246  *
247  *
248  * @see ecore_con_url_custom_new()
249  * @see ecore_con_url_url_set()
250  */
251 EAPI Ecore_Con_Url *
252 ecore_con_url_new(const char *url)
253 {
254 #ifdef HAVE_CURL
255    Ecore_Con_Url *url_con;
256    CURLcode ret;
257
258    if (!_init_count)
259       return NULL;
260
261    url_con = calloc(1, sizeof(Ecore_Con_Url));
262    if (!url_con)
263       return NULL;
264
265    url_con->fd = -1;
266    url_con->write_fd = -1;
267
268    url_con->curl_easy = curl_easy_init();
269    if (!url_con->curl_easy)
270      {
271         free(url_con);
272         return NULL;
273      }
274
275    ECORE_MAGIC_SET(url_con, ECORE_MAGIC_CON_URL);
276
277    if (!ecore_con_url_url_set(url_con, url))
278      {
279         ecore_con_url_free(url_con);
280         return NULL;
281      }
282
283    ret = curl_easy_setopt(url_con->curl_easy, CURLOPT_ENCODING, "gzip,deflate");
284    if (ret != CURLE_OK)
285      {
286         ERR("Could not set CURLOPT_ENCODING to \"gzip,deflate\": %s",
287             curl_easy_strerror(ret));
288         ecore_con_url_free(url_con);
289         return NULL;
290      }
291
292    curl_easy_setopt(url_con->curl_easy, CURLOPT_WRITEFUNCTION,
293                     _ecore_con_url_data_cb);
294    curl_easy_setopt(url_con->curl_easy, CURLOPT_WRITEDATA, url_con);
295
296    curl_easy_setopt(url_con->curl_easy, CURLOPT_PROGRESSFUNCTION,
297                     _ecore_con_url_progress_cb);
298    curl_easy_setopt(url_con->curl_easy, CURLOPT_PROGRESSDATA, url_con);
299    curl_easy_setopt(url_con->curl_easy, CURLOPT_NOPROGRESS,   EINA_FALSE);
300
301    curl_easy_setopt(url_con->curl_easy, CURLOPT_HEADERFUNCTION,
302                     _ecore_con_url_header_cb);
303    curl_easy_setopt(url_con->curl_easy, CURLOPT_HEADERDATA,     url_con);
304
305    /*
306     * FIXME: Check that these timeouts are sensible defaults
307     * FIXME: Provide a means to change these timeouts
308     */
309    curl_easy_setopt(url_con->curl_easy, CURLOPT_CONNECTTIMEOUT, 30);
310    curl_easy_setopt(url_con->curl_easy, CURLOPT_TIMEOUT,        300);
311    curl_easy_setopt(url_con->curl_easy, CURLOPT_FOLLOWLOCATION, 1);
312
313    return url_con;
314 #else
315    return NULL;
316    url = NULL;
317 #endif
318 }
319
320 /**
321  * Creates a custom connection object.
322  *
323  * Creates and initializes a new Ecore_Con_Url for a custom request (e.g. HEAD,
324  * SUBSCRIBE and other obscure HTTP requests). This object should be used like
325  * one created with ecore_con_url_new().
326  *
327  * @param url URL that will receive requests
328  * @param custom_request Custom request (e.g. GET, POST, HEAD, PUT, etc)
329  *
330  * @return NULL on error, a new Ecore_Con_Url on success.
331  *
332  *
333  * @see ecore_con_url_new()
334  * @see ecore_con_url_url_set()
335  */
336 EAPI Ecore_Con_Url *
337 ecore_con_url_custom_new(const char *url, const char *custom_request)
338 {
339 #ifdef HAVE_CURL
340    Ecore_Con_Url *url_con;
341    CURLcode ret;
342
343    if (!url)
344       return NULL;
345
346    if (!custom_request)
347       return NULL;
348
349    url_con = ecore_con_url_new(url);
350
351    if (!url_con)
352       return NULL;
353
354    ret = curl_easy_setopt(url_con->curl_easy, CURLOPT_CUSTOMREQUEST, custom_request);
355    if (ret != CURLE_OK)
356      {
357         ERR("Could not set a custom request string: %s",
358             curl_easy_strerror(ret));
359         ecore_con_url_free(url_con);
360         return NULL;
361      }
362
363    return url_con;
364 #else
365    return NULL;
366    url = NULL;
367    custom_request = NULL;
368 #endif
369 }
370
371 /**
372  * Destroys a Ecore_Con_Url connection object.
373  *
374  * @param url_con Connection object to free.
375  *
376  * @see ecore_con_url_new()
377  */
378 EAPI void
379 ecore_con_url_free(Ecore_Con_Url *url_con)
380 {
381 #ifdef HAVE_CURL
382    char *s;
383    CURLMcode ret;
384
385    if (!url_con)
386       return;
387
388    if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
389      {
390         ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_free");
391         return;
392      }
393
394    ECORE_MAGIC_SET(url_con, ECORE_MAGIC_NONE);
395    if(url_con->fd != -1)
396      {
397         FD_CLR(url_con->fd, &_current_fd_set);
398         if (url_con->fd_handler)
399            ecore_main_fd_handler_del(url_con->fd_handler);
400
401         url_con->fd = -1;
402         url_con->fd_handler = NULL;
403      }
404
405    if (url_con->curl_easy)
406      {
407         // FIXME: For an unknown reason, progress continue to arrive after destruction
408         // this prevent any further call to the callback.
409         curl_easy_setopt(url_con->curl_easy, CURLOPT_PROGRESSFUNCTION, NULL);
410
411         if (url_con->active)
412           {
413              url_con->active = EINA_FALSE;
414
415              ret = curl_multi_remove_handle(_curlm, url_con->curl_easy);
416              if (ret != CURLM_OK)
417                ERR("curl_multi_remove_handle failed: %s",
418                    curl_multi_strerror(ret));
419           }
420
421         curl_easy_cleanup(url_con->curl_easy);
422      }
423
424    _url_con_list = eina_list_remove(_url_con_list, url_con);
425    curl_slist_free_all(url_con->headers);
426    EINA_LIST_FREE(url_con->additional_headers, s)
427      free(s);
428    EINA_LIST_FREE(url_con->response_headers, s)
429      free(s);
430    free(url_con->url);
431    free(url_con);
432 #else
433    return;
434    url_con = NULL;
435 #endif
436 }
437
438 /**
439  * Sets the URL to send the request to.
440  *
441  * @param url_con Connection object through which the request will be sent.
442  * @param url URL that will receive the request
443  *
444  * @return EINA_TRUE on success, EINA_FALSE on error.
445  *
446  */
447 EAPI Eina_Bool
448 ecore_con_url_url_set(Ecore_Con_Url *url_con, const char *url)
449 {
450 #ifdef HAVE_CURL
451    if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
452      {
453         ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_url_set");
454         return EINA_FALSE;
455      }
456
457    if (url_con->active)
458       return EINA_FALSE;
459
460    if (url_con->url)
461       free(url_con->url);
462
463    url_con->url = NULL;
464    if (url)
465       url_con->url = strdup(url);
466
467    if (url_con->url)
468       curl_easy_setopt(url_con->curl_easy, CURLOPT_URL,
469                        url_con->url);
470    else
471       curl_easy_setopt(url_con->curl_easy, CURLOPT_URL, "");
472
473    return EINA_TRUE;
474 #else
475    return EINA_FALSE;
476    (void)url;
477    (void)url_con;
478 #endif
479 }
480
481 /**
482  * Associates data with a connection object.
483  *
484  * Associates data with a connection object, which can be retrieved later with
485  * ecore_con_url_data_get()).
486  *
487  * @param url_con Connection object to associate data.
488  * @param data Data to be set.
489  *
490  *
491  * @see ecore_con_url_data_get()
492  */
493 EAPI void
494 ecore_con_url_data_set(Ecore_Con_Url *url_con, void *data)
495 {
496 #ifdef HAVE_CURL
497    if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
498      {
499         ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_data_set");
500         return;
501      }
502
503    url_con->data = data;
504 #else
505    return;
506    url_con = NULL;
507    data = NULL;
508 #endif
509 }
510
511 /**
512  * Adds an additional header to the request connection object.
513  *
514  * Adds an additional header to the request connection object. This addition
515  * will be valid for only one ecore_con_url_send() call.
516  *
517  * @param url_con Connection object
518  * @param key Header key
519  * @param value Header value
520  *
521  *
522  * @see ecore_con_url_send()
523  * @see ecore_con_url_additional_headers_clear()
524  */
525 EAPI void
526 ecore_con_url_additional_header_add(Ecore_Con_Url *url_con, const char *key,
527                                     const char *value)
528 {
529 #ifdef HAVE_CURL
530    char *tmp;
531
532    if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
533      {
534         ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL,
535                          "ecore_con_url_additional_header_add");
536         return;
537      }
538
539    tmp = malloc(strlen(key) + strlen(value) + 3);
540    if (!tmp)
541       return;
542
543    sprintf(tmp, "%s: %s", key, value);
544    url_con->additional_headers = eina_list_append(url_con->additional_headers,
545                                                   tmp);
546 #else
547    return;
548    url_con = NULL;
549    key = NULL;
550    value = NULL;
551 #endif
552 }
553
554 /**
555  * Cleans additional headers.
556  *
557  * Cleans additional headers associated with a connection object (previously
558  * added with ecore_con_url_additional_header_add()).
559  *
560  * @param url_con Connection object to clean additional headers.
561  *
562  *
563  * @see ecore_con_url_additional_header_add()
564  * @see ecore_con_url_send()
565  */
566 EAPI void
567 ecore_con_url_additional_headers_clear(Ecore_Con_Url *url_con)
568 {
569 #ifdef HAVE_CURL
570    char *s;
571
572    if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
573      {
574         ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL,
575                          "ecore_con_url_additional_headers_clear");
576         return;
577      }
578
579    EINA_LIST_FREE(url_con->additional_headers, s)
580      free(s);
581 #else
582    return;
583    url_con = NULL;
584 #endif
585 }
586
587 /**
588  * Retrieves data associated with a Ecore_Con_Url connection object.
589  *
590  * Retrieves data associated with a Ecore_Con_Url connection object (previously
591  * set with ecore_con_url_data_set()).
592  *
593  * @param url_con Connection object to retrieve data from.
594  *
595  * @return Data associated with the given object.
596  *
597  *
598  * @see ecore_con_url_data_set()
599  */
600 EAPI void *
601 ecore_con_url_data_get(Ecore_Con_Url *url_con)
602 {
603 #ifdef HAVE_CURL
604    if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
605      {
606         ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_data_get");
607         return NULL;
608      }
609
610    return url_con->data;
611 #else
612    return NULL;
613    url_con = NULL;
614 #endif
615 }
616
617 /**
618  * Sets whether HTTP requests should be conditional, dependent on
619  * modification time.
620  *
621  * @param url_con   Ecore_Con_Url to act upon.
622  * @param condition Condition to use for HTTP requests.
623  * @param timestamp Time since 1 Jan 1970 to use in the condition.
624  *
625  * @sa ecore_con_url_send()
626  */
627 EAPI void
628 ecore_con_url_time(Ecore_Con_Url *url_con, Ecore_Con_Url_Time condition,
629                    double timestamp)
630 {
631 #ifdef HAVE_CURL
632    if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
633      {
634         ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_time");
635         return;
636      }
637
638    url_con->time_condition = condition;
639    url_con->timestamp = timestamp;
640 #else
641    return;
642    (void)url_con;
643    (void)condition;
644    (void)timestamp;
645 #endif
646 }
647
648 /**
649  * Setup a file for receiving request data.
650  *
651  * Setups a file to have response data written into. Note that
652  * ECORE_CON_EVENT_URL_DATA events will not be emitted if a file has been set to
653  * receive the response data.
654  *
655  * @param url_con Connection object to set file
656  * @param fd File descriptor associated with the file
657  *
658  */
659 EAPI void
660 ecore_con_url_fd_set(Ecore_Con_Url *url_con, int fd)
661 {
662 #ifdef HAVE_CURL
663    if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
664      {
665         ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_set");
666         return;
667      }
668
669    url_con->write_fd = fd;
670 #endif
671 }
672
673 /**
674  * Retrieves the number of bytes received.
675  *
676  * Retrieves the number of bytes received on the last request of the given
677  * connection object.
678  *
679  * @param url_con Connection object which the request was sent on.
680  *
681  * @return Number of bytes received on request.
682  *
683  *
684  * @see ecore_con_url_send()
685  */
686 EAPI int
687 ecore_con_url_received_bytes_get(Ecore_Con_Url *url_con)
688 {
689 #ifdef HAVE_CURL
690    if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
691      {
692         ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL,
693                          "ecore_con_url_received_bytes_get");
694         return -1;
695      }
696
697    return url_con->received;
698 #else
699    return 0;
700 #endif
701 }
702
703 /**
704  * Retrieves headers from last request sent.
705  *
706  * Retrieves a list containing the response headers. This function should be
707  * used after an ECORE_CON_EVENT_URL_COMPLETE event (headers should normally be
708  * ready at that time).
709  *
710  * @param url_con Connection object to retrieve response headers from.
711  *
712  * @return List of response headers. This list must not be modified by the user.
713  *
714  */
715 EAPI const Eina_List *
716 ecore_con_url_response_headers_get(Ecore_Con_Url *url_con)
717 {
718 #ifdef HAVE_CURL
719    return url_con->response_headers;
720 #else
721    return NULL;
722 #endif
723 }
724
725 /**
726  * Sets url_con to use http auth, with given username and password, "safely" or not.
727  *
728  * @param url_con Connection object to perform a request on, previously created
729  *    with ecore_con_url_new() or ecore_con_url_custom_new().
730  * @param username Username to use in authentication
731  * @param password Password to use in authentication
732  * @param safe Whether to use "safer" methods (eg, NOT http basic auth)
733  *
734  * @return #EINA_TRUE on success, #EINA_FALSE on error.
735  *
736  */
737 EAPI Eina_Bool
738 ecore_con_url_httpauth_set(Ecore_Con_Url *url_con, const char *username,
739                            const char *password, Eina_Bool safe)
740 {
741 #ifdef HAVE_CURL
742    CURLcode ret;
743
744    if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
745      {
746         ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL,
747                          "ecore_con_url_httpauth_set");
748         return EINA_FALSE;
749      }
750
751 # if LIBCURL_VERSION_NUM >= 0x071301
752    if ((username) && (password))
753      {
754         if (safe)
755            curl_easy_setopt(url_con->curl_easy, CURLOPT_HTTPAUTH,
756                             CURLAUTH_ANYSAFE);
757         else
758            curl_easy_setopt(url_con->curl_easy, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
759
760         ret = curl_easy_setopt(url_con->curl_easy, CURLOPT_USERNAME, username);
761         if (ret != CURLE_OK)
762           {
763              ERR("Could not set username for HTTP authentication: %s",
764                  curl_easy_strerror(ret));
765              return EINA_FALSE;
766           }
767
768         ret = curl_easy_setopt(url_con->curl_easy, CURLOPT_PASSWORD, password);
769         if (ret != CURLE_OK)
770           {
771              ERR("Could not set password for HTTP authentication: %s",
772                  curl_easy_strerror(ret));
773              return EINA_FALSE;
774           }
775
776         return EINA_TRUE;
777      }
778 # endif
779 #endif
780
781    return EINA_FALSE;
782 }
783
784 /**
785  * Sends a request.
786  *
787  * @param url_con Connection object to perform a request on, previously created
788  *                with ecore_con_url_new() or ecore_con_url_custom_new().
789  * @param data    Payload (data sent on the request)
790  * @param length  Payload length. If @c -1, rely on automatic length
791  *                calculation via @c strlen() on @p data.
792  * @param content_type Content type of the payload (e.g. text/xml)
793  *
794  * @return #EINA_TRUE on success, #EINA_FALSE on error.
795  *
796  * @see ecore_con_url_custom_new()
797  * @see ecore_con_url_additional_headers_clear()
798  * @see ecore_con_url_additional_header_add()
799  * @see ecore_con_url_data_set()
800  * @see ecore_con_url_data_get()
801  * @see ecore_con_url_response_headers_get()
802  * @see ecore_con_url_time()
803  */
804 EAPI Eina_Bool
805 ecore_con_url_send(Ecore_Con_Url *url_con, const void *data, long length,
806                    const char *content_type)
807 {
808 #ifdef HAVE_CURL
809    Eina_List *l;
810    const char *s;
811    char tmp[256];
812
813    if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
814      {
815         ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_send");
816         return EINA_FALSE;
817      }
818
819    if (url_con->active)
820       return EINA_FALSE;
821
822    if (!url_con->url)
823       return EINA_FALSE;
824
825    /* Free response headers from previous send() calls */
826    EINA_LIST_FREE(url_con->response_headers, s)
827      free((char *)s);
828    url_con->response_headers = NULL;
829
830    curl_slist_free_all(url_con->headers);
831    url_con->headers = NULL;
832
833    if (data)
834      {
835         if ((content_type) && (strlen(content_type) < 200))
836           {
837              snprintf(tmp, sizeof(tmp), "Content-Type: %s", content_type);
838              url_con->headers = curl_slist_append(url_con->headers, tmp);
839           }
840
841         curl_easy_setopt(url_con->curl_easy, CURLOPT_POSTFIELDS,    data);
842         curl_easy_setopt(url_con->curl_easy, CURLOPT_POSTFIELDSIZE, length);
843      }
844
845    switch (url_con->time_condition)
846      {
847       case ECORE_CON_URL_TIME_NONE:
848          curl_easy_setopt(url_con->curl_easy, CURLOPT_TIMECONDITION,
849                           CURL_TIMECOND_NONE);
850          break;
851
852       case ECORE_CON_URL_TIME_IFMODSINCE:
853          curl_easy_setopt(url_con->curl_easy, CURLOPT_TIMECONDITION,
854                           CURL_TIMECOND_IFMODSINCE);
855          curl_easy_setopt(url_con->curl_easy, CURLOPT_TIMEVALUE,
856                           (long)url_con->timestamp);
857          break;
858
859       case ECORE_CON_URL_TIME_IFUNMODSINCE:
860          curl_easy_setopt(url_con->curl_easy, CURLOPT_TIMECONDITION,
861                           CURL_TIMECOND_IFUNMODSINCE);
862          curl_easy_setopt(url_con->curl_easy, CURLOPT_TIMEVALUE,
863                           (long)url_con->timestamp);
864          break;
865      }
866
867    /* Additional headers */
868    EINA_LIST_FOREACH(url_con->additional_headers, l, s)
869      url_con->headers = curl_slist_append(url_con->headers, s);
870
871    curl_easy_setopt(url_con->curl_easy, CURLOPT_HTTPHEADER, url_con->headers);
872
873    url_con->received = 0;
874
875    return _ecore_con_url_perform(url_con);
876 #else
877    return EINA_FALSE;
878    url_con = NULL;
879    data = NULL;
880    length = 0;
881    content_type = NULL;
882 #endif
883 }
884
885 /**
886  * @brief Uploads a file to an ftp site.
887  * @param url_con The Ecore_Con_Url object to send with
888  * @param filename The path to the file to send
889  * @param user The username to log in with
890  * @param pass The password to log in with
891  * @param upload_dir The directory to which the file should be uploaded
892  * @return #EINA_TRUE on success, else #EINA_FALSE.
893  * Upload @p filename to an ftp server set in @p url_con using @p user
894  * and @p pass to directory @p upload_dir
895  */
896 EAPI Eina_Bool
897 ecore_con_url_ftp_upload(Ecore_Con_Url *url_con, const char *filename,
898                          const char *user, const char *pass,
899                          const char *upload_dir)
900 {
901 #ifdef HAVE_CURL
902    char url[4096];
903    char userpwd[4096];
904    FILE *fd;
905    struct stat file_info;
906    CURLcode ret;
907
908    if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
909      {
910         ECORE_MAGIC_FAIL(url_con,
911                          ECORE_MAGIC_CON_URL,
912                          "ecore_con_url_ftp_upload");
913         return EINA_FALSE;
914      }
915
916    if (url_con->active)
917       return EINA_FALSE;
918
919    if (!url_con->url)
920       return EINA_FALSE;
921
922    if (filename)
923      {
924         if (stat(filename, &file_info))
925            return EINA_FALSE;
926
927         snprintf(userpwd, sizeof(userpwd), "%s:%s", user, pass);
928         ret = curl_easy_setopt(url_con->curl_easy, CURLOPT_USERPWD, userpwd);
929         if (ret != CURLE_OK)
930           {
931              ERR("Could not set username and password for FTP upload: %s",
932                  curl_easy_strerror(ret));
933              return EINA_FALSE;
934           }
935
936         char tmp[PATH_MAX];
937         snprintf(tmp, PATH_MAX, "%s", filename);
938
939         if (upload_dir)
940           snprintf(url, sizeof(url), "ftp://%s/%s/%s", url_con->url,
941                    upload_dir, basename(tmp));
942         else
943           snprintf(url, sizeof(url), "ftp://%s/%s",    url_con->url,
944                    basename(tmp));
945
946         if (!ecore_con_url_url_set(url_con, url))
947           return EINA_FALSE;
948
949         curl_easy_setopt(url_con->curl_easy, CURLOPT_INFILESIZE_LARGE,
950                          (curl_off_t)file_info.st_size);
951         curl_easy_setopt(url_con->curl_easy, CURLOPT_UPLOAD,   1);
952         curl_easy_setopt(url_con->curl_easy, CURLOPT_READFUNCTION,
953                          _ecore_con_url_read_cb);
954
955         fd = fopen(filename, "rb");
956         if (!fd)
957           {
958              ERR("Could not open \"%s\" for FTP upload", filename);
959              return EINA_FALSE;
960           }
961         curl_easy_setopt(url_con->curl_easy, CURLOPT_READDATA, fd);
962
963         return _ecore_con_url_perform(url_con);
964      }
965
966    return EINA_FALSE;
967 #else
968    return EINA_FALSE;
969    (void)url_con;
970    (void)filename;
971    (void)user;
972    (void)pass;
973    (void)upload_dir;
974 #endif
975 }
976
977 /**
978  * Toggle libcurl's verbose output.
979  *
980  * If @p verbose is @c EINA_TRUE, libcurl will output a lot of verbose
981  * information about its operations, which is useful for
982  * debugging. The verbose information will be sent to stderr.
983  *
984  * @param url_con Ecore_Con_Url instance which will be acted upon.
985  * @param verbose Whether or not to enable libcurl's verbose output.
986  */
987 EAPI void
988 ecore_con_url_verbose_set(Ecore_Con_Url *url_con, Eina_Bool verbose)
989 {
990 #ifdef HAVE_CURL
991    if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
992      {
993         ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL,
994                          "ecore_con_url_verbose_set");
995         return;
996      }
997
998    if (url_con->active)
999       return;
1000
1001    if (!url_con->url)
1002       return;
1003
1004    curl_easy_setopt(url_con->curl_easy, CURLOPT_VERBOSE, (int)verbose);
1005 #else
1006    (void)url_con;
1007    (void)verbose;
1008 #endif
1009 }
1010
1011 /**
1012  * Enable or disable EPSV extension
1013  * @return  FIXME: To be more documented.
1014  */
1015 EAPI void
1016 ecore_con_url_ftp_use_epsv_set(Ecore_Con_Url *url_con, Eina_Bool use_epsv)
1017 {
1018 #ifdef HAVE_CURL
1019    if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
1020      {
1021         ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL,
1022                          "ecore_con_url_ftp_use_epsv_set");
1023         return;
1024      }
1025
1026    if (url_con->active)
1027       return;
1028
1029    if (!url_con->url)
1030       return;
1031
1032    curl_easy_setopt(url_con->curl_easy, CURLOPT_FTP_USE_EPSV, (int)use_epsv);
1033 #endif
1034    (void)url_con;
1035    (void)use_epsv;
1036 }
1037
1038 /**
1039  * @}
1040  */
1041
1042 #ifdef HAVE_CURL
1043 static int
1044 _ecore_con_url_suspend_fd_handler(void)
1045 {
1046    Eina_List *l;
1047    Ecore_Con_Url *url_con;
1048    int deleted = 0;
1049
1050    if (!_url_con_list)
1051       return 0;
1052
1053    EINA_LIST_FOREACH(_url_con_list, l, url_con)
1054    {
1055       if (url_con->active && url_con->fd_handler)
1056         {
1057            ecore_main_fd_handler_del(url_con->fd_handler);
1058            url_con->fd_handler = NULL;
1059            deleted++;
1060         }
1061    }
1062
1063    return deleted;
1064 }
1065
1066 static int
1067 _ecore_con_url_restart_fd_handler(void)
1068 {
1069    Eina_List *l;
1070    Ecore_Con_Url *url_con;
1071    int activated = 0;
1072
1073    if (!_url_con_list)
1074       return 0;
1075
1076    EINA_LIST_FOREACH(_url_con_list, l, url_con)
1077    {
1078       if (!url_con->fd_handler && url_con->fd != -1)
1079         {
1080            url_con->fd_handler =
1081               ecore_main_fd_handler_add(url_con->fd, url_con->flags,
1082                                         _ecore_con_url_fd_handler,
1083                                         NULL, NULL, NULL);
1084            activated++;
1085         }
1086    }
1087
1088    return activated;
1089 }
1090
1091 static size_t
1092 _ecore_con_url_data_cb(void *buffer, size_t size, size_t nitems, void *userp)
1093 {
1094    Ecore_Con_Url *url_con;
1095    Ecore_Con_Event_Url_Data *e;
1096    size_t real_size = size * nitems;
1097
1098    url_con = (Ecore_Con_Url *)userp;
1099
1100    if (!url_con)
1101       return -1;
1102
1103    if (!ECORE_MAGIC_CHECK(url_con, ECORE_MAGIC_CON_URL))
1104      {
1105         ECORE_MAGIC_FAIL(url_con, ECORE_MAGIC_CON_URL, "ecore_con_url_data_cb");
1106         return -1;
1107      }
1108
1109    url_con->received += real_size;
1110
1111    if (url_con->write_fd < 0)
1112      {
1113         e =
1114            malloc(sizeof(Ecore_Con_Event_Url_Data) + sizeof(unsigned char) *
1115                   (real_size - 1));
1116         if (e)
1117           {
1118              e->url_con = url_con;
1119              e->size = real_size;
1120              memcpy(e->data, buffer, real_size);
1121                 ecore_event_add(ECORE_CON_EVENT_URL_DATA, e,
1122                              _ecore_con_event_url_free, NULL);
1123           }
1124      }
1125    else
1126      {
1127         ssize_t count = 0;
1128         size_t total_size = real_size;
1129         size_t offset = 0;
1130
1131         while (total_size > 0)
1132           {
1133              count = write(url_con->write_fd,
1134                            (char *)buffer + offset,
1135                            total_size);
1136              if (count < 0)
1137                {
1138                   if (errno != EAGAIN && errno != EINTR)
1139                      return -1;
1140                }
1141              else
1142                {
1143                   total_size -= count;
1144                   offset += count;
1145                }
1146           }
1147      }
1148
1149    return real_size;
1150 }
1151
1152 #define ECORE_CON_URL_TRANSMISSION(Transmit, Event, Url_con, Total, Now) \
1153    {                                                                                                                               \
1154       Ecore_Con_Event_Url_Progress *e; \
1155       if ((Total != 0) || (Now != 0)) \
1156         {                                                                                                                               \
1157            e = calloc(1, sizeof(Ecore_Con_Event_Url_Progress)); \
1158            if (e) \
1159              {                                                                                                                               \
1160                 e->url_con = url_con;                                           \
1161                 e->total = Total;                                                                                                                               \
1162                 e->now = Now;                                           \
1163                 ecore_event_add(Event, e, _ecore_con_event_url_free, NULL);                                                                                     \
1164              }                                                                                                                               \
1165         }                                                                                                                               \
1166    }
1167
1168 static size_t
1169 _ecore_con_url_header_cb(void *ptr, size_t size, size_t nitems, void *stream)
1170 {
1171    size_t real_size = size * nitems;
1172    Ecore_Con_Url *url_con = stream;
1173
1174    char *header = malloc(sizeof(char) * (real_size + 1));
1175    if (!header)
1176       return real_size;
1177
1178    memcpy(header, ptr, real_size);
1179    header[real_size] = '\0';
1180
1181    url_con->response_headers = eina_list_append(url_con->response_headers,
1182                                                 header);
1183
1184    return real_size;
1185 }
1186
1187 static int
1188 _ecore_con_url_progress_cb(void *clientp, double dltotal, double dlnow,
1189                            double ultotal,
1190                            double ulnow)
1191 {
1192    Ecore_Con_Event_Url_Progress *e;
1193    Ecore_Con_Url *url_con;
1194
1195    url_con = clientp;
1196
1197    e = malloc(sizeof(Ecore_Con_Event_Url_Progress));
1198    if (e)
1199      {
1200         e->url_con = url_con;
1201         e->down.total = dltotal;
1202         e->down.now = dlnow;
1203         e->up.total = ultotal;
1204         e->up.now = ulnow;
1205         ecore_event_add(ECORE_CON_EVENT_URL_PROGRESS, e,
1206                         _ecore_con_event_url_free, NULL);
1207      }
1208
1209    return 0;
1210 }
1211
1212 static size_t
1213 _ecore_con_url_read_cb(void *ptr, size_t size, size_t nitems, void *stream)
1214 {
1215    size_t retcode = fread(ptr, size, nitems, stream);
1216
1217    if (ferror((FILE *)stream))
1218      {
1219         fclose(stream);
1220         return CURL_READFUNC_ABORT;
1221      }
1222    else if ((retcode == 0) || (retcode < nitems))
1223      {
1224         fclose((FILE *)stream);
1225         return 0;
1226      }
1227
1228    INF("*** We read %zu bytes from file", retcode);
1229    return retcode;
1230 }
1231
1232 static Eina_Bool
1233 _ecore_con_url_perform(Ecore_Con_Url *url_con)
1234 {
1235    fd_set read_set, write_set, exc_set;
1236    int fd_max, fd;
1237    int flags, still_running;
1238    int completed_immediately = 0;
1239    CURLMcode ret;
1240
1241    _url_con_list = eina_list_append(_url_con_list, url_con);
1242
1243    url_con->active = EINA_TRUE;
1244    curl_multi_add_handle(_curlm, url_con->curl_easy);
1245    /* This one can't be stopped, or the download never start. */
1246    while (curl_multi_perform(_curlm, &still_running) == CURLM_CALL_MULTI_PERFORM) ;
1247
1248    completed_immediately = _ecore_con_url_process_completed_jobs(url_con);
1249
1250    if (!completed_immediately)
1251      {
1252         if (url_con->fd_handler)
1253            ecore_main_fd_handler_del(url_con->fd_handler);
1254
1255         url_con->fd_handler = NULL;
1256
1257         /* url_con still active -- set up an fd_handler */
1258         FD_ZERO(&read_set);
1259         FD_ZERO(&write_set);
1260         FD_ZERO(&exc_set);
1261
1262         /* Stupid curl, why can't I get the fd to the current added job? */
1263         ret = curl_multi_fdset(_curlm, &read_set, &write_set, &exc_set,
1264                                &fd_max);
1265         if (ret != CURLM_OK)
1266           {
1267              ERR("curl_multi_fdset failed: %s", curl_multi_strerror(ret));
1268              return EINA_FALSE;
1269           }
1270
1271         for (fd = 0; fd <= fd_max; fd++)
1272           {
1273              if (!FD_ISSET(fd, &_current_fd_set))
1274                {
1275                   flags = 0;
1276                   if (FD_ISSET(fd, &read_set))
1277                      flags |= ECORE_FD_READ;
1278
1279                   if (FD_ISSET(fd, &write_set))
1280                      flags |= ECORE_FD_WRITE;
1281
1282                   if (FD_ISSET(fd, &exc_set))
1283                      flags |= ECORE_FD_ERROR;
1284
1285                   if (flags)
1286                     {
1287                        long ms = 0;
1288
1289                        ret = curl_multi_timeout(_curlm, &ms);
1290                        if (ret != CURLM_OK)
1291                          ERR("curl_multi_timeout failed: %s",
1292                              curl_multi_strerror(ret));
1293
1294                        if (ms == 0)
1295                           ms = 1000;
1296
1297                        FD_SET(fd, &_current_fd_set);
1298                        url_con->fd = fd;
1299                        url_con->flags = flags;
1300                        url_con->fd_handler =
1301                           ecore_main_fd_handler_add(fd, flags,
1302                                                     _ecore_con_url_fd_handler,
1303                                                     NULL, NULL, NULL);
1304                        break;
1305                     }
1306                }
1307           }
1308         if (!url_con->fd_handler)
1309           {
1310              /* Failed to set up an fd_handler */
1311              ecore_timer_freeze(_curl_timeout);
1312
1313              ret = curl_multi_remove_handle(_curlm, url_con->curl_easy);
1314              if (ret != CURLM_OK)
1315                ERR("curl_multi_remove_handle failed: %s",
1316                    curl_multi_strerror(ret));
1317
1318              url_con->active = EINA_FALSE;
1319              url_con->fd = -1;
1320              return EINA_FALSE;
1321           }
1322
1323         ecore_timer_thaw(_curl_timeout);
1324      }
1325
1326    return EINA_TRUE;
1327 }
1328
1329 static Eina_Bool
1330 _ecore_con_url_idler_handler(void *data)
1331 {
1332    double start;
1333    int done = 1, still_running;
1334
1335    start = ecore_time_get();
1336    while (curl_multi_perform(_curlm, &still_running) == CURLM_CALL_MULTI_PERFORM)
1337       /* make this not more than a frametime to keep interactivity high */
1338       if ((ecore_time_get() - start) > ecore_animator_frametime_get())
1339         {
1340            done = 0;
1341            break;
1342         }
1343
1344    _ecore_con_url_process_completed_jobs(NULL);
1345
1346    if (done)
1347      {
1348         _ecore_con_url_restart_fd_handler();
1349         _fd_idler_handler = NULL;
1350
1351         if (!_url_con_list)
1352            ecore_timer_freeze(_curl_timeout);
1353
1354         return data ==
1355                (void *)0xACE ? ECORE_CALLBACK_RENEW : ECORE_CALLBACK_CANCEL;
1356      }
1357
1358    return ECORE_CALLBACK_RENEW;
1359 }
1360
1361 static Eina_Bool
1362 _ecore_con_url_fd_handler(void *data __UNUSED__,
1363                           Ecore_Fd_Handler *fd_handler __UNUSED__)
1364 {
1365    _ecore_con_url_suspend_fd_handler();
1366
1367    if (!_fd_idler_handler)
1368       _fd_idler_handler = ecore_idler_add(
1369             _ecore_con_url_idler_handler, NULL);
1370
1371    return ECORE_CALLBACK_RENEW;
1372 }
1373
1374 static int
1375 _ecore_con_url_process_completed_jobs(Ecore_Con_Url *url_con_to_match)
1376 {
1377    Eina_List *l;
1378    Ecore_Con_Url *url_con;
1379    Ecore_Con_Event_Url_Complete *e;
1380    CURLMsg *curlmsg;
1381    CURLMcode ret;
1382    int n_remaining;
1383    int job_matched = 0;
1384
1385    /* Loop jobs and check if any are done */
1386    while ((curlmsg = curl_multi_info_read(_curlm, &n_remaining)))
1387      {
1388         if (curlmsg->msg != CURLMSG_DONE)
1389            continue;
1390
1391         /* find the job which is done */
1392         EINA_LIST_FOREACH(_url_con_list, l, url_con)
1393         {
1394            if (curlmsg->easy_handle == url_con->curl_easy)
1395              {
1396                 if (url_con_to_match &&
1397                     (url_con == url_con_to_match))
1398                    job_matched = 1;
1399
1400                 if(url_con->fd != -1)
1401                   {
1402                      FD_CLR(url_con->fd, &_current_fd_set);
1403                      if (url_con->fd_handler)
1404                         ecore_main_fd_handler_del(
1405                            url_con->fd_handler);
1406
1407                      url_con->fd = -1;
1408                      url_con->fd_handler = NULL;
1409                   }
1410
1411                 _url_con_list = eina_list_remove(_url_con_list, url_con);
1412                 url_con->active = EINA_FALSE;
1413                 e = calloc(1, sizeof(Ecore_Con_Event_Url_Complete));
1414                 if (e)
1415                   {
1416                      e->url_con = url_con;
1417                      e->status = 0;
1418                      if (curlmsg->data.result == CURLE_OK)
1419                        {
1420                           long status; /* curl API uses long, not int */
1421
1422                           status = 0;
1423                           curl_easy_getinfo(curlmsg->easy_handle,
1424                                             CURLINFO_RESPONSE_CODE,
1425                                             &status);
1426                           e->status = status;
1427                        }
1428
1429                      _url_complete_push_event(ECORE_CON_EVENT_URL_COMPLETE, e);
1430                   }
1431
1432                 ret = curl_multi_remove_handle(_curlm, url_con->curl_easy);
1433                 if (ret != CURLM_OK)
1434                   ERR("curl_multi_remove_handle failed: %s",
1435                       curl_multi_strerror(ret));
1436
1437                 break;
1438              }
1439         }
1440      }
1441
1442    return job_matched;
1443 }
1444
1445 static void
1446 _ecore_con_event_url_free(void *data __UNUSED__, void *ev)
1447 {
1448    free(ev);
1449 }
1450
1451 #endif