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