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