From b791c79ca01bc2ea4b091d82d7125c0ea4bc29d0 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Sat, 20 Aug 2016 00:55:26 -0300 Subject: [PATCH] WIP: efl.net: Introduce Efl.Net.Dialer.Http This class implements the Efl.Net.Dialer interface using libcurl to perform HTTP requests. That means it's an Efl.Net.Dialer, Efl.Net.Socket, Efl.Io.Reader, Efl.Io.Writer and Efl.Io.Closer, thus being usable with Efl.Io.Copier as demonstrated in the efl_io_copier_example.c --- src/Makefile_Ecore_Con.am | 11 +- src/examples/ecore/.gitignore | 1 + src/examples/ecore/Makefile.am | 10 +- src/examples/ecore/efl_io_copier_example.c | 113 ++ .../ecore/efl_net_dialer_http_example.c | 414 ++++ src/lib/ecore_con/Ecore_Con_Eo.h | 59 + src/lib/ecore_con/ecore_con_url_curl.c | 203 ++ src/lib/ecore_con/ecore_con_url_curl.h | 235 ++- src/lib/ecore_con/efl_net_dialer_http.c | 1699 +++++++++++++++++ src/lib/ecore_con/efl_net_dialer_http.eo | 314 +++ src/lib/ecore_con/efl_net_http_types.eot | 176 ++ 11 files changed, 3224 insertions(+), 11 deletions(-) create mode 100644 src/examples/ecore/efl_net_dialer_http_example.c create mode 100644 src/lib/ecore_con/efl_net_dialer_http.c create mode 100644 src/lib/ecore_con/efl_net_dialer_http.eo create mode 100644 src/lib/ecore_con/efl_net_http_types.eot diff --git a/src/Makefile_Ecore_Con.am b/src/Makefile_Ecore_Con.am index ec98fd31dd..ceb2e90692 100644 --- a/src/Makefile_Ecore_Con.am +++ b/src/Makefile_Ecore_Con.am @@ -11,6 +11,7 @@ ecore_con_eolian_files = \ lib/ecore_con/efl_net_socket_tcp.eo \ lib/ecore_con/efl_net_dialer.eo \ lib/ecore_con/efl_net_dialer_tcp.eo \ + lib/ecore_con/efl_net_dialer_http.eo \ lib/ecore_con/efl_net_server.eo \ lib/ecore_con/efl_net_server_fd.eo \ lib/ecore_con/efl_net_server_tcp.eo \ @@ -19,8 +20,13 @@ ecore_con_eolian_files = \ lib/ecore_con/ecore_con_eet_client_obj.eo \ lib/ecore_con/efl_network_url.eo +ecore_con_eolian_type_files = \ + lib/ecore_con/efl_net_http_types.eot + + ecore_con_eolian_c = $(ecore_con_eolian_files:%.eo=%.eo.c) ecore_con_eolian_h = $(ecore_con_eolian_files:%.eo=%.eo.h) \ + $(ecore_con_eolian_type_files:%.eot=%.eot.h) \ $(ecore_con_eolian_files:%.eo=%.eo.legacy.h) BUILT_SOURCES += \ @@ -29,11 +35,13 @@ BUILT_SOURCES += \ ecoreconeolianfilesdir = $(datadir)/eolian/include/ecore-@VMAJ@ ecoreconeolianfiles_DATA = \ - $(ecore_con_eolian_files) + $(ecore_con_eolian_files) \ + $(ecore_con_eolian_type_files) EXTRA_DIST2 += \ ${ecoreconeolianfiles_DATA} + lib_LTLIBRARIES += lib/ecore_con/libecore_con.la installed_ecoreconmainheadersdir = $(includedir)/ecore-con-@VMAJ@ @@ -66,6 +74,7 @@ lib/ecore_con/efl_net_socket_fd.c \ lib/ecore_con/efl_net_socket_tcp.c \ lib/ecore_con/efl_net_dialer.c \ lib/ecore_con/efl_net_dialer_tcp.c \ +lib/ecore_con/efl_net_dialer_http.c \ lib/ecore_con/efl_net_server.c \ lib/ecore_con/efl_net_server_fd.c \ lib/ecore_con/efl_net_server_tcp.c diff --git a/src/examples/ecore/.gitignore b/src/examples/ecore/.gitignore index 31e2bc0613..6339345b44 100644 --- a/src/examples/ecore/.gitignore +++ b/src/examples/ecore/.gitignore @@ -48,3 +48,4 @@ /ecore_buffer_provider_example /efl_io_copier_example /efl_net_server_example +/efl_net_dialer_http_example diff --git a/src/examples/ecore/Makefile.am b/src/examples/ecore/Makefile.am index 17d6d2bd92..2e0f470fe5 100644 --- a/src/examples/ecore/Makefile.am +++ b/src/examples/ecore/Makefile.am @@ -78,7 +78,8 @@ ecore_getopt_example \ ecore_con_eet_client_example \ ecore_con_eet_server_example \ efl_io_copier_example \ -efl_net_server_example +efl_net_server_example \ +efl_net_dialer_http_example ECORE_COMMON_LDADD = \ $(top_builddir)/src/lib/ecore/libecore.la \ @@ -285,6 +286,9 @@ efl_io_copier_example_LDADD = $(ECORE_CON_COMMON_LDADD) efl_net_server_example_SOURCES = efl_net_server_example.c efl_net_server_example_LDADD = $(ECORE_CON_COMMON_LDADD) +efl_net_dialer_http_example_SOURCES = efl_net_dialer_http_example.c +efl_net_dialer_http_example_LDADD = $(ECORE_CON_COMMON_LDADD) + SRCS = \ ecore_animator_example.c \ ecore_buffer_example.c \ @@ -331,7 +335,9 @@ ecore_getopt_example.c \ ecore_con_eet_client_example.c \ ecore_con_eet_server_example.c \ ecore_con_eet_descriptor_example.c \ -efl_io_copier_example.c +efl_io_copier_example.c \ +efl_net_server_example.c \ +efl_net_dialer_http_example.c DATA_FILES = red.png Makefile.examples diff --git a/src/examples/ecore/efl_io_copier_example.c b/src/examples/ecore/efl_io_copier_example.c index be5352bff5..6fee399920 100644 --- a/src/examples/ecore/efl_io_copier_example.c +++ b/src/examples/ecore/efl_io_copier_example.c @@ -89,6 +89,63 @@ EFL_CALLBACKS_ARRAY_DEFINE(dialer_cbs, { EFL_NET_DIALER_EVENT_ERROR, _dialer_error }, { EFL_NET_DIALER_EVENT_CONNECTED, _dialer_connected }); +static void +_http_headers_done(void *data EINA_UNUSED, const Eo_Event *event) +{ + Eina_Iterator *itr; + Efl_Net_Http_Header *h; + Efl_Net_Http_Version ver = efl_net_dialer_http_version_get(event->object); + const char *response_content_type; + int64_t response_content_length; + + fprintf(stderr, "INFO: HTTP/%d.%d connected to '%s', code=%d, headers:\n", + ver / 100, ver % 100, + efl_net_dialer_address_dial_get(event->object), + efl_net_dialer_http_response_status_get(event->object)); + + /* this is only for the last request, if allow_redirects and you want + * all of the headers, use efl_net_dialer_http_response_headers_all_get() + */ + itr = efl_net_dialer_http_response_headers_get(event->object); + + EINA_ITERATOR_FOREACH(itr, h) + fprintf(stderr, "INFO: Header '%s: %s'\n", h->key, h->value); + + eina_iterator_free(itr); + + /* be nice to memory, we do not need these anymore */ + efl_net_dialer_http_response_headers_clear(event->object); + + response_content_length = efl_net_dialer_http_response_content_length_get(event->object); + response_content_type = efl_net_dialer_http_response_content_type_get(event->object); + fprintf(stderr, "INFO: Download %" PRId64 " bytes of type %s\n", + response_content_length, response_content_type); + + if (efl_net_dialer_http_primary_mode_get(event->object) == EFL_NET_DIALER_HTTP_PRIMARY_MODE_UPLOAD) + { + int64_t request_content_type = efl_net_dialer_http_request_content_length_get(event->object); + fprintf(stderr, "INFO: Upload %" PRId64 " bytes\n", + request_content_type); + } +} + +static void +_http_closed(void *data EINA_UNUSED, const Eo_Event *event) +{ + uint64_t dn, dt, un, ut; + + efl_net_dialer_http_progress_download_get(event->object, &dn, &dt); + efl_net_dialer_http_progress_upload_get(event->object, &un, &ut); + fprintf(stderr, "INFO: http transfer info: " + "download=%" PRIu64 "/%" PRIu64 " " + "upload=%" PRIu64 "/%" PRIu64 "\n", + dn, dt, un, ut); +} + +EFL_CALLBACKS_ARRAY_DEFINE(http_cbs, + { EFL_NET_DIALER_HTTP_EVENT_HEADERS_DONE, _http_headers_done }, + { EFL_IO_CLOSER_EVENT_CLOSED, _http_closed }); + /* copier events are of interest, you should hook to at least "done" * and "error" */ @@ -274,6 +331,7 @@ static const Ecore_Getopt options = { "The input file name or:\n" ":stdin: to read from stdin.\n" "tcp://IP:PORT to connect using TCP and an IPv4 (A.B.C.D:PORT) or IPv6 ([A:B:C:D::E]:PORT).\n" + "http://address to do a GET request\n" "", "input-file"), ECORE_GETOPT_STORE_METAVAR_STR(0, NULL, @@ -283,6 +341,7 @@ static const Ecore_Getopt options = { ":memory: to write to a memory buffer.\n" ":none: to not use a destination object.\n" "tcp://IP:PORT to connect using TCP and an IPv4 (A.B.C.D:PORT) or IPv6 ([A:B:C:D::E]:PORT).\n" + "http://address to do a PUT request\n" "", "output-file"), ECORE_GETOPT_SENTINEL @@ -321,6 +380,7 @@ main(int argc, char **argv) ecore_init(); ecore_con_init(); + ecore_con_url_init(); args = ecore_getopt_parse(&options, values, argc, argv); if (args < 0) @@ -386,6 +446,32 @@ main(int argc, char **argv) goto end_input; } } + else if (strncmp(input_fname, "http://", strlen("http://")) == 0 || + strncmp(input_fname, "https://", strlen("https://")) == 0) + { + Eina_Error err; + + input = efl_add(EFL_NET_DIALER_HTTP_CLASS, ecore_main_loop_get(), + efl_net_dialer_http_method_set(efl_self, "GET"), + efl_event_callback_array_add(efl_self, input_cbs(), NULL), /* optional */ + efl_event_callback_array_add(efl_self, dialer_cbs(), NULL), /* optional */ + efl_event_callback_array_add(efl_self, http_cbs(), NULL) /* optional */ + ); + if (!input) + { + fprintf(stderr, "ERROR: could not create HTTP Dialer.\n"); + retval = EXIT_FAILURE; + goto end; + } + + err = efl_net_dialer_dial(input, input_fname); + if (err) + { + fprintf(stderr, "ERROR: could not HTTP dial %s: %s\n", + input_fname, eina_error_msg_get(err)); + goto end_input; + } + } else { /* regular file, open with flags: read-only and close-on-exec */ @@ -493,6 +579,32 @@ main(int argc, char **argv) goto end_output; } } + else if (strncmp(output_fname, "http://", strlen("http://")) == 0 || + strncmp(output_fname, "https://", strlen("https://")) == 0) + { + Eina_Error err; + + output = efl_add(EFL_NET_DIALER_HTTP_CLASS, ecore_main_loop_get(), + efl_net_dialer_http_method_set(efl_self, "PUT"), + efl_event_callback_array_add(efl_self, output_cbs(), NULL), /* optional */ + efl_event_callback_array_add(efl_self, dialer_cbs(), NULL), /* optional */ + efl_event_callback_array_add(efl_self, http_cbs(), NULL) /* optional */ + ); + if (!output) + { + fprintf(stderr, "ERROR: could not create HTTP Dialer.\n"); + retval = EXIT_FAILURE; + goto end_input; + } + + err = efl_net_dialer_dial(output, output_fname); + if (err) + { + fprintf(stderr, "ERROR: could not HTTP dial %s: %s\n", + output_fname, eina_error_msg_get(err)); + goto end_output; + } + } else { /* regular file, open with flags: write-only, close-on-exec, @@ -560,6 +672,7 @@ main(int argc, char **argv) input = NULL; end: + ecore_con_url_shutdown(); ecore_con_shutdown(); ecore_shutdown(); diff --git a/src/examples/ecore/efl_net_dialer_http_example.c b/src/examples/ecore/efl_net_dialer_http_example.c new file mode 100644 index 0000000000..2ba9f896d1 --- /dev/null +++ b/src/examples/ecore/efl_net_dialer_http_example.c @@ -0,0 +1,414 @@ +#define EFL_BETA_API_SUPPORT 1 +#define EFL_EO_API_SUPPORT 1 +#include +#include +#include +#include +#include + +static int retval = EXIT_SUCCESS; +static int waiting; + +static void +_closed(void *data EINA_UNUSED, const Eo_Event *event) +{ + fprintf(stderr, "INFO: closed %s\n", + efl_name_get(event->object)); +} + +static void +_eos(void *data EINA_UNUSED, const Eo_Event *event) +{ + fprintf(stderr, "INFO: eos %s\n", + efl_name_get(event->object)); +} + +static void +_connected(void *data EINA_UNUSED, const Eo_Event *event) +{ + fprintf(stderr, "INFO: connected %s\n", + efl_net_dialer_address_dial_get(event->object)); +} + +static void +_resolved(void *data EINA_UNUSED, const Eo_Event *event) +{ + fprintf(stderr, "INFO: resolved %s => %s\n", + efl_net_dialer_address_dial_get(event->object), + efl_net_socket_address_remote_get(event->object)); +} + +static void +_error(void *data EINA_UNUSED, const Eo_Event *event) +{ + const Eina_Error *perr = event->info; + fprintf(stderr, "INFO: error: %d '%s'\n", *perr, eina_error_msg_get(*perr)); + retval = EXIT_FAILURE; +} + +static void +_http_headers_done(void *data EINA_UNUSED, const Eo_Event *event) +{ + Eo *o = event->object; + Efl_Net_Http_Version ver = efl_net_dialer_http_version_get(o); + Eina_Iterator *itr; + const Efl_Net_Http_Header *h; + + fprintf(stderr, "INFO: HTTP/%d.%d status=%d url=%s\n", + ver / 100, ver % 100, + efl_net_dialer_http_response_status_get(o), + efl_net_socket_address_remote_get(o)); + + itr = efl_net_dialer_http_response_headers_get(o); + EINA_ITERATOR_FOREACH(itr, h) + fprintf(stderr, "INFO: %s: %s\n", h->key, h->value); + + eina_iterator_free(itr); + + fprintf(stderr, "INFO: content-type: %s, content-length: %" PRId64 "\n", + efl_net_dialer_http_response_content_type_get(o), + efl_net_dialer_http_response_content_length_get(o)); + + fprintf(stderr, "INFO: to upload %zd bytes\n", efl_net_dialer_http_request_content_length_get(o)); +} + +EFL_CALLBACKS_ARRAY_DEFINE(dialer_cbs, + { EFL_NET_DIALER_HTTP_EVENT_HEADERS_DONE, _http_headers_done }, + { EFL_NET_DIALER_EVENT_CONNECTED, _connected }, + { EFL_NET_DIALER_EVENT_RESOLVED, _resolved }, + { EFL_NET_DIALER_EVENT_ERROR, _error }, + { EFL_IO_CLOSER_EVENT_CLOSED, _closed }, + { EFL_IO_READER_EVENT_EOS, _eos }); + +static void +_done(void *data EINA_UNUSED, const Eo_Event *event) +{ + waiting--; + fprintf(stderr, "INFO: done %s, waiting=%d\n", + efl_name_get(event->object), waiting); + if (waiting == 0) + ecore_main_loop_quit(); +} + +EFL_CALLBACKS_ARRAY_DEFINE(copier_cbs, + { EFL_IO_COPIER_EVENT_DONE, _done }, + { EFL_IO_CLOSER_EVENT_CLOSED, _closed }); + +static const char *primary_mode_choices[] = { + "auto", + "download", + "upload", + NULL +}; + +static Efl_Net_Dialer_Http_Primary_Mode +_parse_primary_mode(const char *str) +{ + if (strcmp(str, "upload") == 0) + return EFL_NET_DIALER_HTTP_PRIMARY_MODE_UPLOAD; + if (strcmp(str, "download") == 0) + return EFL_NET_DIALER_HTTP_PRIMARY_MODE_DOWNLOAD; + + return EFL_NET_DIALER_HTTP_PRIMARY_MODE_AUTO; +} + +static const char *http_version_choices[] = { + "1.0", + "1.1", + "2.0", + NULL +}; + +static Efl_Net_Http_Version +_parse_http_version(const char *str) +{ + if (strcmp(str, "1.1") == 0) + return EFL_NET_HTTP_VERSION_V1_1; + if (strcmp(str, "2.0") == 0) + return EFL_NET_HTTP_VERSION_V2_0; + + return EFL_NET_HTTP_VERSION_V1_0; +} + +static const char *authentication_method_choices[] = { + "none", + "basic", + "digest", + "negotiate", + "ntlm", + "ntlm_winbind", + "any_safe", + "any", + NULL, +}; + +static Efl_Net_Http_Authentication_Method +_parse_authentication_method(const char *str) +{ + if (strcmp(str, "basic") == 0) + return EFL_NET_HTTP_AUTHENTICATION_METHOD_BASIC; + if (strcmp(str, "digest") == 0) + return EFL_NET_HTTP_AUTHENTICATION_METHOD_DIGEST; + if (strcmp(str, "negotiate") == 0) + return EFL_NET_HTTP_AUTHENTICATION_METHOD_NEGOTIATE; + if (strcmp(str, "ntlm") == 0) + return EFL_NET_HTTP_AUTHENTICATION_METHOD_NTLM; + if (strcmp(str, "ntlm_winbind") == 0) + return EFL_NET_HTTP_AUTHENTICATION_METHOD_NTLM_WINBIND; + if (strcmp(str, "any_safe") == 0) + return EFL_NET_HTTP_AUTHENTICATION_METHOD_ANY_SAFE; + if (strcmp(str, "any") == 0) + return EFL_NET_HTTP_AUTHENTICATION_METHOD_ANY; + + return EFL_NET_HTTP_AUTHENTICATION_METHOD_NONE; +} + +static const Ecore_Getopt options = { + "efl_net_dialer_http_example", /* program name */ + NULL, /* usage line */ + "1", /* version */ + "(C) 2016 Enlightenment Project", /* copyright */ + "BSD 2-Clause", /* license */ + /* long description, may be multiline and contain \n */ + "Example of Efl_Net_Dialer_Http usage.\n", + EINA_FALSE, + { + ECORE_GETOPT_STORE_STR('m', "method", "HTTP method such as GET, POST, PUT..."), + ECORE_GETOPT_CHOICE('M', "primary-mode", "Define primary operation mode.", primary_mode_choices), + ECORE_GETOPT_CHOICE('v', "http-version", "HTTP protocol version to use", http_version_choices), + ECORE_GETOPT_STORE_STR('U', "username", "Authentication username"), + ECORE_GETOPT_STORE_STR('P', "password", "Authentication password"), + ECORE_GETOPT_CHOICE('A', "authentication-method", "Authentication method", authentication_method_choices), + ECORE_GETOPT_STORE_BOOL('R', "authentication-restricted", "Authentication method must be restricted"), + ECORE_GETOPT_STORE_BOOL('r', "allow-redirects", "allow redirections by following 'Location:' headers"), + ECORE_GETOPT_STORE_DOUBLE('t', "connect-timeout", "timeout in seconds for the connection phase"), + ECORE_GETOPT_APPEND('H', "header", "Add custom headers. Format must be 'Key: Value'", ECORE_GETOPT_TYPE_STR), + ECORE_GETOPT_APPEND('F', "form", "Add form field. Format must be 'key=value'", ECORE_GETOPT_TYPE_STR), + ECORE_GETOPT_STORE_STR('i', "input-file", "Input file to use when uploading"), + ECORE_GETOPT_VERSION('V', "version"), + ECORE_GETOPT_COPYRIGHT('C', "copyright"), + ECORE_GETOPT_LICENSE('L', "license"), + ECORE_GETOPT_HELP('h', "help"), + ECORE_GETOPT_STORE_METAVAR_STR(0, NULL, + "The address (URL) to dial", "address"), + ECORE_GETOPT_STORE_METAVAR_STR(0, NULL, + "The output file name or :stdout:", + "output-file"), + ECORE_GETOPT_SENTINEL + } +}; + +int +main(int argc, char **argv) +{ + char *method = "GET"; + char *primary_mode_str = "auto"; + char *http_version_str = "1.1"; + char *username = NULL; + char *password = NULL; + char *authentication_method_str = "basic"; + char *address = NULL; + char *output_fname = NULL; + char *input_fname = NULL; + Eina_Bool quit_option = EINA_FALSE; + Eina_Bool authentication_restricted = EINA_FALSE; + Eina_Bool allow_redirects = EINA_TRUE; + double timeout_dial = 30.0; + Eina_List *headers = NULL; + Eina_List *form_fields = NULL; + Ecore_Getopt_Value values[] = { + ECORE_GETOPT_VALUE_STR(method), + ECORE_GETOPT_VALUE_STR(primary_mode_str), + ECORE_GETOPT_VALUE_STR(http_version_str), + ECORE_GETOPT_VALUE_STR(username), + ECORE_GETOPT_VALUE_STR(password), + ECORE_GETOPT_VALUE_STR(authentication_method_str), + ECORE_GETOPT_VALUE_BOOL(authentication_restricted), + ECORE_GETOPT_VALUE_BOOL(allow_redirects), + ECORE_GETOPT_VALUE_DOUBLE(timeout_dial), + ECORE_GETOPT_VALUE_LIST(headers), + ECORE_GETOPT_VALUE_LIST(form_fields), + ECORE_GETOPT_VALUE_STR(input_fname), + + /* standard block to provide version, copyright, license and help */ + ECORE_GETOPT_VALUE_BOOL(quit_option), /* -V/--version quits */ + ECORE_GETOPT_VALUE_BOOL(quit_option), /* -C/--copyright quits */ + ECORE_GETOPT_VALUE_BOOL(quit_option), /* -L/--license quits */ + ECORE_GETOPT_VALUE_BOOL(quit_option), /* -h/--help quits */ + + /* positional argument */ + ECORE_GETOPT_VALUE_STR(address), + ECORE_GETOPT_VALUE_STR(output_fname), + + ECORE_GETOPT_VALUE_NONE /* sentinel */ + }; + int args; + Eo *input, *dialer, *output, *sender, *receiver, *loop; + Efl_Net_Dialer_Http_Primary_Mode primary_mode; + Efl_Net_Http_Version http_version; + Efl_Net_Http_Authentication_Method authentication_method; + Efl_Net_Http_Header *header; + Eina_Iterator *itr; + Eina_Error err; + char *str; + + ecore_init(); + ecore_con_init(); + ecore_con_url_init(); + + args = ecore_getopt_parse(&options, values, argc, argv); + if (args < 0) + { + fputs("ERROR: Could not parse command line options.\n", stderr); + retval = EXIT_FAILURE; + goto end; + } + + if (quit_option) goto end; + + loop = ecore_main_loop_get(); + + args = ecore_getopt_parse_positional(&options, values, argc, argv, args); + if (args < 0) + { + fputs("ERROR: Could not parse positional arguments.\n", stderr); + retval = EXIT_FAILURE; + goto end; + } + + if ((!input_fname) || (strcmp(input_fname, ":stdin:") == 0)) + { + input = efl_add(EFL_IO_STDIN_CLASS, loop, + efl_name_set(efl_self, "stdin"), + efl_event_callback_add(efl_self, EFL_IO_READER_EVENT_EOS, _eos, NULL)); + } + else + { + input = efl_add(EFL_IO_FILE_CLASS, loop, + efl_name_set(efl_self, "input"), + efl_file_set(efl_self, input_fname, NULL), + efl_io_file_flags_set(efl_self, O_RDONLY | O_CLOEXEC), + efl_event_callback_add(efl_self, EFL_IO_READER_EVENT_EOS, _eos, NULL)); + } + + if ((!output_fname) || (strcmp(output_fname, ":stdout:") == 0)) + { + output = efl_add(EFL_IO_STDOUT_CLASS, loop, + efl_name_set(efl_self, "stdout")); + } + else + { + output = efl_add(EFL_IO_FILE_CLASS, loop, + efl_name_set(efl_self, "output"), + efl_file_set(efl_self, output_fname, NULL), + efl_io_file_mode_set(efl_self, 0644), + efl_io_file_flags_set(efl_self, O_WRONLY | O_CLOEXEC | O_TRUNC | O_CREAT)); + } + + primary_mode = _parse_primary_mode(primary_mode_str); + http_version = _parse_http_version(http_version_str); + authentication_method = _parse_authentication_method(authentication_method_str); + + dialer = efl_add(EFL_NET_DIALER_HTTP_CLASS, loop, + efl_name_set(efl_self, "dialer"), + efl_net_dialer_http_method_set(efl_self, method), + efl_net_dialer_http_primary_mode_set(efl_self, primary_mode), + efl_net_dialer_http_version_set(efl_self, http_version), + efl_net_dialer_http_authentication_set(efl_self, username, password, authentication_method, authentication_restricted), + efl_net_dialer_http_allow_redirects_set(efl_self, allow_redirects), + efl_net_dialer_timeout_dial_set(efl_self, timeout_dial), + efl_event_callback_array_add(efl_self, dialer_cbs(), NULL)); + + EINA_LIST_FREE(headers, str) + { + char *p = strchr(str, ':'); + if (p) + { + p[0] = '\0'; + p++; + while ((p[0] != '\0') && isspace(p[0])) + p++; + } + efl_net_dialer_http_request_header_add(dialer, str, p); + free(str); + } + + EINA_LIST_FREE(form_fields, str) + { + fprintf(stderr, "TODO: form_field %s", str); + free(str); + } + + waiting = 1; + if (efl_net_dialer_http_primary_mode_get(dialer) == EFL_NET_DIALER_HTTP_PRIMARY_MODE_UPLOAD) + waiting++; + + sender = efl_add(EFL_IO_COPIER_CLASS, loop, + efl_name_set(efl_self, "sender"), + efl_io_copier_source_set(efl_self, input), + efl_io_copier_destination_set(efl_self, dialer), + efl_event_callback_array_add(efl_self, copier_cbs(), NULL)); + + fprintf(stderr, "INFO: sending %s->%s\n", + efl_name_get(input), + efl_name_get(dialer)); + + receiver = efl_add(EFL_IO_COPIER_CLASS, loop, + efl_name_set(efl_self, "receiver"), + efl_io_copier_source_set(efl_self, dialer), + efl_io_copier_destination_set(efl_self, output), + efl_event_callback_array_add(efl_self, copier_cbs(), NULL)); + fprintf(stderr, "INFO: receiving %s->%s\n", + efl_name_get(dialer), + efl_name_get(output)); + + err = efl_net_dialer_dial(dialer, address); + if (err != 0) + { + fprintf(stderr, "ERROR: could not dial '%s': %s", + address, eina_error_msg_get(err)); + goto no_mainloop; + } + + fprintf(stderr, + "INFO: dialed %s\n" + "INFO: - method=%s\n" + "INFO: - primary_mode=%d\n" + "INFO: - version=%d\n" + "INFO: - allow_redirects=%d\n" + "INFO: - timeout_dial=%fs\n" + "INFO: - request headers:\n", + efl_net_dialer_address_dial_get(dialer), + efl_net_dialer_http_method_get(dialer), + efl_net_dialer_http_primary_mode_get(dialer), + efl_net_dialer_http_version_get(dialer), + efl_net_dialer_http_allow_redirects_get(dialer), + efl_net_dialer_timeout_dial_get(dialer)); + + itr = efl_net_dialer_http_request_headers_get(dialer); + EINA_ITERATOR_FOREACH(itr, header) + fprintf(stderr, "INFO: %s: %s\n", header->key, header->value); + eina_iterator_free(itr); + + ecore_main_loop_begin(); + + fprintf(stderr, "INFO: main loop finished.\n"); + + no_mainloop: + efl_io_closer_close(sender); + efl_del(sender); + + efl_io_closer_close(receiver); + efl_del(receiver); + + efl_del(dialer); + efl_del(output); + efl_del(input); + + end: + ecore_con_url_shutdown(); + ecore_con_shutdown(); + ecore_shutdown(); + + return retval; +} diff --git a/src/lib/ecore_con/Ecore_Con_Eo.h b/src/lib/ecore_con/Ecore_Con_Eo.h index c185aea437..22f1e70b49 100644 --- a/src/lib/ecore_con/Ecore_Con_Eo.h +++ b/src/lib/ecore_con/Ecore_Con_Eo.h @@ -14,3 +14,62 @@ #include "efl_net_socket_tcp.eo.h" #include "efl_net_dialer_tcp.eo.h" #include "efl_net_server_tcp.eo.h" + +#include "efl_net_http_types.eot.h" +/* TODO: should be generated from 'var Efl.Net.Http.Error.*' */ +extern Eina_Error EFL_NET_HTTP_ERROR_BAD_CONTENT_ENCODING; +extern Eina_Error EFL_NET_HTTP_ERROR_BAD_DOWNLOAD_RESUME; +extern Eina_Error EFL_NET_HTTP_ERROR_BAD_FUNCTION_ARGUMENT; +extern Eina_Error EFL_NET_HTTP_ERROR_CHUNK_FAILED; +extern Eina_Error EFL_NET_HTTP_ERROR_CONV_FAILED; +extern Eina_Error EFL_NET_HTTP_ERROR_CONV_REQD; +extern Eina_Error EFL_NET_HTTP_ERROR_COULDNT_CONNECT; +extern Eina_Error EFL_NET_HTTP_ERROR_COULDNT_RESOLVE_HOST; +extern Eina_Error EFL_NET_HTTP_ERROR_COULDNT_RESOLVE_PROXY; +extern Eina_Error EFL_NET_HTTP_ERROR_FAILED_INIT; +extern Eina_Error EFL_NET_HTTP_ERROR_FILE_COULDNT_READ_FILE; +extern Eina_Error EFL_NET_HTTP_ERROR_FILESIZE_EXCEEDED; +extern Eina_Error EFL_NET_HTTP_ERROR_FUNCTION_NOT_FOUND; +extern Eina_Error EFL_NET_HTTP_ERROR_GOT_NOTHING; +extern Eina_Error EFL_NET_HTTP_ERROR_HTTP2; +extern Eina_Error EFL_NET_HTTP_ERROR_HTTP2_STREAM; +extern Eina_Error EFL_NET_HTTP_ERROR_HTTP_POST_ERROR; +extern Eina_Error EFL_NET_HTTP_ERROR_HTTP_RETURNED_ERROR; +extern Eina_Error EFL_NET_HTTP_ERROR_INTERFACE_FAILED; +extern Eina_Error EFL_NET_HTTP_ERROR_LOGIN_DENIED; +extern Eina_Error EFL_NET_HTTP_ERROR_NO_CONNECTION_AVAILABLE; +extern Eina_Error EFL_NET_HTTP_ERROR_NOT_BUILT_IN; +extern Eina_Error EFL_NET_HTTP_ERROR_OPERATION_TIMEDOUT; +extern Eina_Error EFL_NET_HTTP_ERROR_PARTIAL_FILE; +extern Eina_Error EFL_NET_HTTP_ERROR_PEER_FAILED_VERIFICATION; +extern Eina_Error EFL_NET_HTTP_ERROR_RANGE_ERROR; +extern Eina_Error EFL_NET_HTTP_ERROR_READ_ERROR; +extern Eina_Error EFL_NET_HTTP_ERROR_RECV_ERROR; +extern Eina_Error EFL_NET_HTTP_ERROR_REMOTE_ACCESS_DENIED; +extern Eina_Error EFL_NET_HTTP_ERROR_REMOTE_DISK_FULL; +extern Eina_Error EFL_NET_HTTP_ERROR_REMOTE_FILE_EXISTS; +extern Eina_Error EFL_NET_HTTP_ERROR_REMOTE_FILE_NOT_FOUND; +extern Eina_Error EFL_NET_HTTP_ERROR_SEND_ERROR; +extern Eina_Error EFL_NET_HTTP_ERROR_SEND_FAIL_REWIND; +extern Eina_Error EFL_NET_HTTP_ERROR_SSL_CACERT; +extern Eina_Error EFL_NET_HTTP_ERROR_SSL_CACERT_BADFILE; +extern Eina_Error EFL_NET_HTTP_ERROR_SSL_CERTPROBLEM; +extern Eina_Error EFL_NET_HTTP_ERROR_SSL_CIPHER; +extern Eina_Error EFL_NET_HTTP_ERROR_SSL_CONNECT_ERROR; +extern Eina_Error EFL_NET_HTTP_ERROR_SSL_CRL_BADFILE; +extern Eina_Error EFL_NET_HTTP_ERROR_SSL_ENGINE_INITFAILED; +extern Eina_Error EFL_NET_HTTP_ERROR_SSL_ENGINE_NOTFOUND; +extern Eina_Error EFL_NET_HTTP_ERROR_SSL_ENGINE_SETFAILED; +extern Eina_Error EFL_NET_HTTP_ERROR_SSL_INVALIDCERTSTATUS; +extern Eina_Error EFL_NET_HTTP_ERROR_SSL_ISSUER_ERROR; +extern Eina_Error EFL_NET_HTTP_ERROR_SSL_PINNEDPUBKEYNOTMATCH; +extern Eina_Error EFL_NET_HTTP_ERROR_SSL_SHUTDOWN_FAILED; +extern Eina_Error EFL_NET_HTTP_ERROR_TOO_MANY_REDIRECTS; +extern Eina_Error EFL_NET_HTTP_ERROR_UNKNOWN_OPTION; +extern Eina_Error EFL_NET_HTTP_ERROR_UNSUPPORTED_PROTOCOL; +extern Eina_Error EFL_NET_HTTP_ERROR_UPLOAD_FAILED; +extern Eina_Error EFL_NET_HTTP_ERROR_URL_MALFORMAT; +extern Eina_Error EFL_NET_HTTP_ERROR_USE_SSL_FAILED; +extern Eina_Error EFL_NET_HTTP_ERROR_WRITE_ERROR; + +#include "efl_net_dialer_http.eo.h" diff --git a/src/lib/ecore_con/ecore_con_url_curl.c b/src/lib/ecore_con/ecore_con_url_curl.c index e241ecd6c1..92becd3741 100644 --- a/src/lib/ecore_con/ecore_con_url_curl.c +++ b/src/lib/ecore_con/ecore_con_url_curl.c @@ -26,6 +26,204 @@ Ecore_Con_Curl *_c = NULL; Eina_Bool _c_fail = EINA_FALSE; double _c_timeout = 0.0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_BAD_CONTENT_ENCODING = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_BAD_DOWNLOAD_RESUME = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_BAD_FUNCTION_ARGUMENT = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_CHUNK_FAILED = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_CONV_FAILED = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_CONV_REQD = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_COULDNT_CONNECT = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_COULDNT_RESOLVE_HOST = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_COULDNT_RESOLVE_PROXY = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_FAILED_INIT = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_FILE_COULDNT_READ_FILE = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_FILESIZE_EXCEEDED = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_FUNCTION_NOT_FOUND = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_GOT_NOTHING = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_HTTP2 = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_HTTP2_STREAM = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_HTTP_POST_ERROR = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_HTTP_RETURNED_ERROR = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_INTERFACE_FAILED = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_LOGIN_DENIED = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_NO_CONNECTION_AVAILABLE = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_NOT_BUILT_IN = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_OPERATION_TIMEDOUT = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_PARTIAL_FILE = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_PEER_FAILED_VERIFICATION = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_RANGE_ERROR = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_READ_ERROR = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_RECV_ERROR = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_REMOTE_ACCESS_DENIED = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_REMOTE_DISK_FULL = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_REMOTE_FILE_EXISTS = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_REMOTE_FILE_NOT_FOUND = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_SEND_ERROR = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_SEND_FAIL_REWIND = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_SSL_CACERT = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_SSL_CACERT_BADFILE = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_SSL_CERTPROBLEM = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_SSL_CIPHER = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_SSL_CONNECT_ERROR = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_SSL_CRL_BADFILE = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_SSL_ENGINE_INITFAILED = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_SSL_ENGINE_NOTFOUND = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_SSL_ENGINE_SETFAILED = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_SSL_INVALIDCERTSTATUS = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_SSL_ISSUER_ERROR = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_SSL_PINNEDPUBKEYNOTMATCH = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_SSL_SHUTDOWN_FAILED = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_TOO_MANY_REDIRECTS = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_UNKNOWN_OPTION = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_UNSUPPORTED_PROTOCOL = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_UPLOAD_FAILED = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_URL_MALFORMAT = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_USE_SSL_FAILED = 0; +EAPI Eina_Error EFL_NET_HTTP_ERROR_WRITE_ERROR = 0; + +Eina_Error +_curlcode_to_eina_error(const CURLcode code) +{ + switch (code) { + case CURLE_OK: return 0; + case CURLE_ABORTED_BY_CALLBACK: return ECONNABORTED; + case CURLE_AGAIN: return EAGAIN; + case CURLE_OUT_OF_MEMORY: return ENOMEM; + +#define _MAP(n) case CURLE_ ## n: return EFL_NET_HTTP_ERROR_ ## n + + _MAP(BAD_CONTENT_ENCODING); + _MAP(BAD_DOWNLOAD_RESUME); + _MAP(BAD_FUNCTION_ARGUMENT); + _MAP(CHUNK_FAILED); + _MAP(CONV_FAILED); + _MAP(CONV_REQD); + _MAP(COULDNT_CONNECT); + _MAP(COULDNT_RESOLVE_HOST); + _MAP(COULDNT_RESOLVE_PROXY); + _MAP(FAILED_INIT); + _MAP(FILE_COULDNT_READ_FILE); + _MAP(FILESIZE_EXCEEDED); + _MAP(FUNCTION_NOT_FOUND); + _MAP(GOT_NOTHING); + _MAP(HTTP2); + _MAP(HTTP2_STREAM); + _MAP(HTTP_POST_ERROR); + _MAP(HTTP_RETURNED_ERROR); + _MAP(INTERFACE_FAILED); + _MAP(LOGIN_DENIED); + _MAP(NO_CONNECTION_AVAILABLE); + _MAP(NOT_BUILT_IN); + _MAP(OPERATION_TIMEDOUT); + _MAP(PARTIAL_FILE); + _MAP(PEER_FAILED_VERIFICATION); + _MAP(RANGE_ERROR); + _MAP(READ_ERROR); + _MAP(RECV_ERROR); + _MAP(REMOTE_ACCESS_DENIED); + _MAP(REMOTE_DISK_FULL); + _MAP(REMOTE_FILE_EXISTS); + _MAP(REMOTE_FILE_NOT_FOUND); + _MAP(SEND_ERROR); + _MAP(SEND_FAIL_REWIND); + _MAP(SSL_CACERT); + _MAP(SSL_CACERT_BADFILE); + _MAP(SSL_CERTPROBLEM); + _MAP(SSL_CIPHER); + _MAP(SSL_CONNECT_ERROR); + _MAP(SSL_CRL_BADFILE); + _MAP(SSL_ENGINE_INITFAILED); + _MAP(SSL_ENGINE_NOTFOUND); + _MAP(SSL_ENGINE_SETFAILED); + _MAP(SSL_INVALIDCERTSTATUS); + _MAP(SSL_ISSUER_ERROR); + _MAP(SSL_PINNEDPUBKEYNOTMATCH); + _MAP(SSL_SHUTDOWN_FAILED); + _MAP(TOO_MANY_REDIRECTS); + _MAP(UNKNOWN_OPTION); + _MAP(UNSUPPORTED_PROTOCOL); + _MAP(UPLOAD_FAILED); + _MAP(URL_MALFORMAT); + _MAP(USE_SSL_FAILED); + _MAP(WRITE_ERROR); +#undef _MAP + + default: + ERR("unexpected error CURcode=%d '%s', not mapped", + code, _c->curl_easy_strerror(code)); + return EINVAL; + } +} + +static void +_c_init_errors(void) +{ + /* use from system: */ + // CURLE_ABORTED_BY_CALLBACK = ECONNABORTED + // CURLE_AGAIN = EAGAIN + // CURLE_OUT_OF_MEMORY = ENOMEM + + if (EFL_NET_HTTP_ERROR_BAD_CONTENT_ENCODING) return; /* only once in the whole runtime */ + +#define _MAP(n) EFL_NET_HTTP_ERROR_ ## n = eina_error_msg_static_register(_c->curl_easy_strerror(CURLE_ ## n)) + _MAP(BAD_CONTENT_ENCODING); + _MAP(BAD_DOWNLOAD_RESUME); + _MAP(BAD_FUNCTION_ARGUMENT); + _MAP(CHUNK_FAILED); + _MAP(CONV_FAILED); + _MAP(CONV_REQD); + _MAP(COULDNT_CONNECT); + _MAP(COULDNT_RESOLVE_HOST); + _MAP(COULDNT_RESOLVE_PROXY); + _MAP(FAILED_INIT); + _MAP(FILE_COULDNT_READ_FILE); + _MAP(FILESIZE_EXCEEDED); + _MAP(FUNCTION_NOT_FOUND); + _MAP(GOT_NOTHING); + _MAP(HTTP2); + _MAP(HTTP2_STREAM); + _MAP(HTTP_POST_ERROR); + _MAP(HTTP_RETURNED_ERROR); + _MAP(INTERFACE_FAILED); + _MAP(LOGIN_DENIED); + _MAP(NO_CONNECTION_AVAILABLE); + _MAP(NOT_BUILT_IN); + _MAP(OPERATION_TIMEDOUT); + _MAP(PARTIAL_FILE); + _MAP(PEER_FAILED_VERIFICATION); + _MAP(RANGE_ERROR); + _MAP(READ_ERROR); + _MAP(RECV_ERROR); + _MAP(REMOTE_ACCESS_DENIED); + _MAP(REMOTE_DISK_FULL); + _MAP(REMOTE_FILE_EXISTS); + _MAP(REMOTE_FILE_NOT_FOUND); + _MAP(SEND_ERROR); + _MAP(SEND_FAIL_REWIND); + _MAP(SSL_CACERT); + _MAP(SSL_CACERT_BADFILE); + _MAP(SSL_CERTPROBLEM); + _MAP(SSL_CIPHER); + _MAP(SSL_CONNECT_ERROR); + _MAP(SSL_CRL_BADFILE); + _MAP(SSL_ENGINE_INITFAILED); + _MAP(SSL_ENGINE_NOTFOUND); + _MAP(SSL_ENGINE_SETFAILED); + _MAP(SSL_INVALIDCERTSTATUS); + _MAP(SSL_ISSUER_ERROR); + _MAP(SSL_PINNEDPUBKEYNOTMATCH); + _MAP(SSL_SHUTDOWN_FAILED); + _MAP(TOO_MANY_REDIRECTS); + _MAP(UNKNOWN_OPTION); + _MAP(UNSUPPORTED_PROTOCOL); + _MAP(UPLOAD_FAILED); + _MAP(URL_MALFORMAT); + _MAP(USE_SSL_FAILED); + _MAP(WRITE_ERROR); +#undef _MAP +} + + Eina_Bool _c_init(void) { @@ -88,15 +286,20 @@ _c_init(void) SYM(curl_multi_perform); SYM(curl_multi_add_handle); SYM(curl_multi_setopt); + SYM(curl_multi_socket_action); + SYM(curl_multi_assign); SYM(curl_easy_init); SYM(curl_easy_setopt); SYM(curl_easy_strerror); SYM(curl_easy_cleanup); SYM(curl_easy_getinfo); + SYM(curl_easy_pause); SYM(curl_slist_free_all); SYM(curl_slist_append); SYM(curl_version_info); + _c_init_errors(); + // curl_global_init() is not thread safe! if (_c->curl_global_init(CURL_GLOBAL_ALL)) goto error; _c->_curlm = _c->curl_multi_init(); diff --git a/src/lib/ecore_con/ecore_con_url_curl.h b/src/lib/ecore_con/ecore_con_url_curl.h index 2917fe6505..0f6b3756a3 100644 --- a/src/lib/ecore_con/ecore_con_url_curl.h +++ b/src/lib/ecore_con/ecore_con_url_curl.h @@ -1,6 +1,14 @@ #ifndef ECORE_CON_URL_CURL_H #define ECORE_CON_URL_CURL_H 1 +#ifdef USE_CURL_H +/* During development you can set USE_CURL_H to use the system's + * curl.h instead of the local replicated values, it will provide all + * constants and type-checking. + */ +#include +#else + // all the types, defines, enums etc. from curl that we actually USE. // we have to add to this if we use more things from curl not already // defined here. see curl headers to get them from @@ -9,13 +17,173 @@ typedef enum CURLM_CALL_MULTI_PERFORM = -1, CURLM_OK = 0 } CURLMcode; -typedef enum -{ - CURLE_OK = 0, - CURLE_OPERATION_TIMEDOUT = 28 + +#ifndef curl_socket_typedef +/* socket typedef */ +#if defined(WIN32) && !defined(__LWIP_OPT_H__) && !defined(LWIP_HDR_OPT_H) +typedef SOCKET curl_socket_t; +#define CURL_SOCKET_BAD INVALID_SOCKET +#else +typedef int curl_socket_t; +#define CURL_SOCKET_BAD -1 +#endif +#define curl_socket_typedef +#endif /* curl_socket_typedef */ + +#define CURL_POLL_NONE 0 +#define CURL_POLL_IN 1 +#define CURL_POLL_OUT 2 +#define CURL_POLL_INOUT 3 +#define CURL_POLL_REMOVE 4 + +#define CURL_SOCKET_TIMEOUT CURL_SOCKET_BAD + +#define CURL_CSELECT_IN 0x01 +#define CURL_CSELECT_OUT 0x02 +#define CURL_CSELECT_ERR 0x04 + +typedef enum { + CURLINFO_TEXT = 0, + CURLINFO_HEADER_IN, /* 1 */ + CURLINFO_HEADER_OUT, /* 2 */ + CURLINFO_DATA_IN, /* 3 */ + CURLINFO_DATA_OUT, /* 4 */ + CURLINFO_SSL_DATA_IN, /* 5 */ + CURLINFO_SSL_DATA_OUT, /* 6 */ + CURLINFO_END +} curl_infotype; + +typedef enum { + CURLE_OK = 0, + CURLE_UNSUPPORTED_PROTOCOL, /* 1 */ + CURLE_FAILED_INIT, /* 2 */ + CURLE_URL_MALFORMAT, /* 3 */ + CURLE_NOT_BUILT_IN, /* 4 - [was obsoleted in August 2007 for + 7.17.0, reused in April 2011 for 7.21.5] */ + CURLE_COULDNT_RESOLVE_PROXY, /* 5 */ + CURLE_COULDNT_RESOLVE_HOST, /* 6 */ + CURLE_COULDNT_CONNECT, /* 7 */ + CURLE_FTP_WEIRD_SERVER_REPLY, /* 8 */ + CURLE_REMOTE_ACCESS_DENIED, /* 9 a service was denied by the server + due to lack of access - when login fails + this is not returned. */ + CURLE_FTP_ACCEPT_FAILED, /* 10 - [was obsoleted in April 2006 for + 7.15.4, reused in Dec 2011 for 7.24.0]*/ + CURLE_FTP_WEIRD_PASS_REPLY, /* 11 */ + CURLE_FTP_ACCEPT_TIMEOUT, /* 12 - timeout occurred accepting server + [was obsoleted in August 2007 for 7.17.0, + reused in Dec 2011 for 7.24.0]*/ + CURLE_FTP_WEIRD_PASV_REPLY, /* 13 */ + CURLE_FTP_WEIRD_227_FORMAT, /* 14 */ + CURLE_FTP_CANT_GET_HOST, /* 15 */ + CURLE_HTTP2, /* 16 - A problem in the http2 framing layer. + [was obsoleted in August 2007 for 7.17.0, + reused in July 2014 for 7.38.0] */ + CURLE_FTP_COULDNT_SET_TYPE, /* 17 */ + CURLE_PARTIAL_FILE, /* 18 */ + CURLE_FTP_COULDNT_RETR_FILE, /* 19 */ + CURLE_OBSOLETE20, /* 20 - NOT USED */ + CURLE_QUOTE_ERROR, /* 21 - quote command failure */ + CURLE_HTTP_RETURNED_ERROR, /* 22 */ + CURLE_WRITE_ERROR, /* 23 */ + CURLE_OBSOLETE24, /* 24 - NOT USED */ + CURLE_UPLOAD_FAILED, /* 25 - failed upload "command" */ + CURLE_READ_ERROR, /* 26 - couldn't open/read from file */ + CURLE_OUT_OF_MEMORY, /* 27 */ + /* Note: CURLE_OUT_OF_MEMORY may sometimes indicate a conversion error + instead of a memory allocation error if CURL_DOES_CONVERSIONS + is defined + */ + CURLE_OPERATION_TIMEDOUT, /* 28 - the timeout time was reached */ + CURLE_OBSOLETE29, /* 29 - NOT USED */ + CURLE_FTP_PORT_FAILED, /* 30 - FTP PORT operation failed */ + CURLE_FTP_COULDNT_USE_REST, /* 31 - the REST command failed */ + CURLE_OBSOLETE32, /* 32 - NOT USED */ + CURLE_RANGE_ERROR, /* 33 - RANGE "command" didn't work */ + CURLE_HTTP_POST_ERROR, /* 34 */ + CURLE_SSL_CONNECT_ERROR, /* 35 - wrong when connecting with SSL */ + CURLE_BAD_DOWNLOAD_RESUME, /* 36 - couldn't resume download */ + CURLE_FILE_COULDNT_READ_FILE, /* 37 */ + CURLE_LDAP_CANNOT_BIND, /* 38 */ + CURLE_LDAP_SEARCH_FAILED, /* 39 */ + CURLE_OBSOLETE40, /* 40 - NOT USED */ + CURLE_FUNCTION_NOT_FOUND, /* 41 */ + CURLE_ABORTED_BY_CALLBACK, /* 42 */ + CURLE_BAD_FUNCTION_ARGUMENT, /* 43 */ + CURLE_OBSOLETE44, /* 44 - NOT USED */ + CURLE_INTERFACE_FAILED, /* 45 - CURLOPT_INTERFACE failed */ + CURLE_OBSOLETE46, /* 46 - NOT USED */ + CURLE_TOO_MANY_REDIRECTS, /* 47 - catch endless re-direct loops */ + CURLE_UNKNOWN_OPTION, /* 48 - User specified an unknown option */ + CURLE_TELNET_OPTION_SYNTAX, /* 49 - Malformed telnet option */ + CURLE_OBSOLETE50, /* 50 - NOT USED */ + CURLE_PEER_FAILED_VERIFICATION, /* 51 - peer's certificate or fingerprint + wasn't verified fine */ + CURLE_GOT_NOTHING, /* 52 - when this is a specific error */ + CURLE_SSL_ENGINE_NOTFOUND, /* 53 - SSL crypto engine not found */ + CURLE_SSL_ENGINE_SETFAILED, /* 54 - can not set SSL crypto engine as + default */ + CURLE_SEND_ERROR, /* 55 - failed sending network data */ + CURLE_RECV_ERROR, /* 56 - failure in receiving network data */ + CURLE_OBSOLETE57, /* 57 - NOT IN USE */ + CURLE_SSL_CERTPROBLEM, /* 58 - problem with the local certificate */ + CURLE_SSL_CIPHER, /* 59 - couldn't use specified cipher */ + CURLE_SSL_CACERT, /* 60 - problem with the CA cert (path?) */ + CURLE_BAD_CONTENT_ENCODING, /* 61 - Unrecognized/bad encoding */ + CURLE_LDAP_INVALID_URL, /* 62 - Invalid LDAP URL */ + CURLE_FILESIZE_EXCEEDED, /* 63 - Maximum file size exceeded */ + CURLE_USE_SSL_FAILED, /* 64 - Requested FTP SSL level failed */ + CURLE_SEND_FAIL_REWIND, /* 65 - Sending the data requires a rewind + that failed */ + CURLE_SSL_ENGINE_INITFAILED, /* 66 - failed to initialise ENGINE */ + CURLE_LOGIN_DENIED, /* 67 - user, password or similar was not + accepted and we failed to login */ + CURLE_TFTP_NOTFOUND, /* 68 - file not found on server */ + CURLE_TFTP_PERM, /* 69 - permission problem on server */ + CURLE_REMOTE_DISK_FULL, /* 70 - out of disk space on server */ + CURLE_TFTP_ILLEGAL, /* 71 - Illegal TFTP operation */ + CURLE_TFTP_UNKNOWNID, /* 72 - Unknown transfer ID */ + CURLE_REMOTE_FILE_EXISTS, /* 73 - File already exists */ + CURLE_TFTP_NOSUCHUSER, /* 74 - No such user */ + CURLE_CONV_FAILED, /* 75 - conversion failed */ + CURLE_CONV_REQD, /* 76 - caller must register conversion + callbacks using curl_easy_setopt options + CURLOPT_CONV_FROM_NETWORK_FUNCTION, + CURLOPT_CONV_TO_NETWORK_FUNCTION, and + CURLOPT_CONV_FROM_UTF8_FUNCTION */ + CURLE_SSL_CACERT_BADFILE, /* 77 - could not load CACERT file, missing + or wrong format */ + CURLE_REMOTE_FILE_NOT_FOUND, /* 78 - remote file not found */ + CURLE_SSH, /* 79 - error from the SSH layer, somewhat + generic so the error message will be of + interest when this has happened */ + + CURLE_SSL_SHUTDOWN_FAILED, /* 80 - Failed to shut down the SSL + connection */ + CURLE_AGAIN, /* 81 - socket is not ready for send/recv, + wait till it's ready and try again (Added + in 7.18.2) */ + CURLE_SSL_CRL_BADFILE, /* 82 - could not load CRL file, missing or + wrong format (Added in 7.19.0) */ + CURLE_SSL_ISSUER_ERROR, /* 83 - Issuer check failed. (Added in + 7.19.0) */ + CURLE_FTP_PRET_FAILED, /* 84 - a PRET command failed */ + CURLE_RTSP_CSEQ_ERROR, /* 85 - mismatch of RTSP CSeq numbers */ + CURLE_RTSP_SESSION_ERROR, /* 86 - mismatch of RTSP Session Ids */ + CURLE_FTP_BAD_FILE_LIST, /* 87 - unable to parse FTP file list */ + CURLE_CHUNK_FAILED, /* 88 - chunk callback reported error */ + CURLE_NO_CONNECTION_AVAILABLE, /* 89 - No connection available, the + session will be queued */ + CURLE_SSL_PINNEDPUBKEYNOTMATCH, /* 90 - specified pinned public key did not + match */ + CURLE_SSL_INVALIDCERTSTATUS, /* 91 - invalid certificate status */ + CURLE_HTTP2_STREAM, /* 92 - stream error in HTTP/2 framing layer + */ + CURL_LAST /* never use! */ } CURLcode; #define CURLOPTTYPE_LONG 0 #define CURLOPTTYPE_OBJECTPOINT 10000 +#define CURLOPTTYPE_STRINGPOINT 10000 #define CURLOPTTYPE_FUNCTIONPOINT 20000 #define CURLOPTTYPE_OFF_T 30000 #define CINIT(na, t, nu) CURLOPT_ ## na = CURLOPTTYPE_ ## t + nu @@ -29,6 +197,7 @@ typedef enum CINIT(WRITEFUNCTION, FUNCTIONPOINT, 11), CINIT(READFUNCTION, FUNCTIONPOINT, 12), CINIT(POSTFIELDS, OBJECTPOINT, 15), + CINIT(USERAGENT, STRINGPOINT, 18), CINIT(HTTPHEADER, OBJECTPOINT, 23), CINIT(WRITEHEADER, OBJECTPOINT, 29), CINIT(COOKIEFILE, OBJECTPOINT, 31), @@ -40,6 +209,7 @@ typedef enum CINIT(NOBODY, LONG, 44), CINIT(UPLOAD, LONG, 46), CINIT(POST, LONG, 47), + CINIT(PUT, LONG, 54), CINIT(FOLLOWLOCATION, LONG, 52), CINIT(PROGRESSFUNCTION, FUNCTIONPOINT, 56), CINIT(PROGRESSDATA, OBJECTPOINT, 57), @@ -47,18 +217,26 @@ typedef enum CINIT(SSL_VERIFYPEER, LONG, 64), CINIT(CAINFO, OBJECTPOINT, 65), CINIT(CONNECTTIMEOUT, LONG, 78), + CINIT(CONNECTTIMEOUT_MS, LONG, 156), CINIT(HEADERFUNCTION, FUNCTIONPOINT, 79), + CINIT(HTTPGET, LONG, 80), CINIT(COOKIEJAR, OBJECTPOINT, 82), CINIT(HTTP_VERSION, LONG, 84), CINIT(FTP_USE_EPSV, LONG, 85), + CINIT(DEBUGFUNCTION, FUNCTIONPOINT, 94), + CINIT(DEBUGDATA, OBJECTPOINT, 95), CINIT(COOKIESESSION, LONG, 96), CINIT(PROXYTYPE, LONG, 101), CINIT(ACCEPT_ENCODING, OBJECTPOINT, 102), + CINIT(PRIVATE, OBJECTPOINT, 103), CINIT(HTTPAUTH, LONG, 107), CINIT(INFILESIZE_LARGE, OFF_T, 115), + CINIT(POSTFIELDSIZE_LARGE, OFF_T, 120), CINIT(COOKIELIST, OBJECTPOINT, 135), CINIT(USERNAME, OBJECTPOINT, 173), - CINIT(PASSWORD, OBJECTPOINT, 174) + CINIT(PASSWORD, OBJECTPOINT, 174), + CINIT(XFERINFOFUNCTION, FUNCTIONPOINT, 219), +#define CURLOPT_XFERINFODATA CURLOPT_PROGRESSDATA } CURLoption; #define CURLINFO_STRING 0x100000 #define CURLINFO_LONG 0x200000 @@ -68,7 +246,15 @@ typedef enum #define CURLINFO_TYPEMASK 0xf00000 typedef enum { - CURLINFO_RESPONSE_CODE = CURLINFO_LONG + 2, + CURLINFO_EFFECTIVE_URL = CURLINFO_STRING + 1, + CURLINFO_RESPONSE_CODE = CURLINFO_LONG + 2, + CURLINFO_CONTENT_LENGTH_DOWNLOAD = CURLINFO_DOUBLE + 15, + CURLINFO_CONTENT_TYPE = CURLINFO_STRING + 18, + CURLINFO_PRIVATE = CURLINFO_STRING + 21, + CURLINFO_HTTP_VERSION = CURLINFO_LONG + 46, + CURLINFO_OS_ERRNO = CURLINFO_LONG + 25, + CURLINFO_LOCAL_IP = CURLINFO_STRING + 41, + CURLINFO_LOCAL_PORT = CURLINFO_LONG + 42, } CURLINFO; typedef enum { @@ -82,7 +268,11 @@ typedef enum #define CINIT(name, type, num) CURLMOPT_ ## name = CURLOPTTYPE_ ## type + num typedef enum { - CINIT(PIPELINING, LONG, 3) + CINIT(SOCKETFUNCTION, FUNCTIONPOINT, 1), + CINIT(SOCKETDATA, OBJECTPOINT, 2), + CINIT(PIPELINING, LONG, 3), + CINIT(TIMERFUNCTION, FUNCTIONPOINT, 4), + CINIT(TIMERDATA, OBJECTPOINT, 5) } CURLMoption; typedef enum { @@ -92,8 +282,10 @@ typedef enum } curl_TimeCond; enum { + CURL_HTTP_VERSION_NONE = 0, CURL_HTTP_VERSION_1_0 = 1, CURL_HTTP_VERSION_1_1 = 2, + CURL_HTTP_VERSION_2_0 = 3 }; typedef enum { @@ -112,11 +304,26 @@ typedef enum #define CURLOPT_READDATA CURLOPT_INFILE #define CURLOPT_HEADERDATA CURLOPT_WRITEHEADER #define CURLVERSION_NOW CURLVERSION_FOURTH +#define CURLAUTH_NONE ((unsigned long)0) #define CURLAUTH_BASIC (((unsigned long)1) << 0) +#define CURLAUTH_DIGEST (((unsigned long)1)<<1) +#define CURLAUTH_NEGOTIATE (((unsigned long)1)<<2) #define CURLAUTH_DIGEST_IE (((unsigned long)1) << 4) #define CURLAUTH_ANY (~CURLAUTH_DIGEST_IE) -#define CURLAUTH_ANYSAFE (~(CURLAUTH_BASIC | CURLAUTH_DIGEST_IE)) +#define CURLAUTH_NTLM (((unsigned long)1)<<3) +#define CURLAUTH_NTLM_WB (((unsigned long)1)<<5) +#define CURLAUTH_ONLY (((unsigned long)1)<<31) +#define CURLAUTH_ANY (~CURLAUTH_DIGEST_IE) +#define CURLAUTH_ANYSAFE (~(CURLAUTH_BASIC|CURLAUTH_DIGEST_IE)) #define CURL_READFUNC_ABORT 0x10000000 +#define CURL_READFUNC_PAUSE 0x10000001 +#define CURL_WRITEFUNC_PAUSE 0x10000001 + +#define CURLPAUSE_RECV (1<<0) +#define CURLPAUSE_RECV_CONT (0) + +#define CURLPAUSE_SEND (1<<2) +#define CURLPAUSE_SEND_CONT (0) typedef void CURLM; typedef void CURL; @@ -153,6 +360,9 @@ typedef struct } data; } CURLMsg; +#endif /* USE_CURL_H */ + + typedef struct _Ecore_Con_Curl Ecore_Con_Curl; struct _Ecore_Con_Curl @@ -183,11 +393,19 @@ struct _Ecore_Con_Curl CURL *curl_handle); CURLMcode (*curl_multi_setopt)(CURLM *multi_handle, CURLMoption option, ...); + CURLMcode (*curl_multi_socket_action)(CURLM *multi_handle, + curl_socket_t fd, + int ev_bitmask, + int *running_handles); + CURLMcode (*curl_multi_assign)(CURLM *multi_handle, + curl_socket_t sockfd, + void *sockp); CURL *(*curl_easy_init)(void); CURLcode (*curl_easy_setopt)(CURL *curl, CURLoption option, ...); const char *(*curl_easy_strerror)(CURLcode); void (*curl_easy_cleanup)(CURL *curl); CURLcode (*curl_easy_getinfo)(CURL *curl, CURLINFO info, ...); + CURLcode (*curl_easy_pause)(CURL *curl, int bitmask); void (*curl_slist_free_all)(struct curl_slist *); struct curl_slist *(*curl_slist_append)(struct curl_slist *list, const char *string); @@ -202,5 +420,6 @@ extern double _c_timeout; Eina_Bool _c_init(void); void _c_shutdown(void); +Eina_Error _curlcode_to_eina_error(const CURLcode code); #endif diff --git a/src/lib/ecore_con/efl_net_dialer_http.c b/src/lib/ecore_con/efl_net_dialer_http.c new file mode 100644 index 0000000000..f25c9ecd70 --- /dev/null +++ b/src/lib/ecore_con/efl_net_dialer_http.c @@ -0,0 +1,1699 @@ +#define EFL_NET_DIALER_HTTP_PROTECTED 1 +#define EFL_NET_DIALER_PROTECTED 1 +#define EFL_NET_SOCKET_PROTECTED 1 +#define EFL_IO_READER_PROTECTED 1 +#define EFL_IO_WRITER_PROTECTED 1 +#define EFL_IO_CLOSER_PROTECTED 1 +#define EFL_IO_SIZER_PROTECTED 1 + +#ifdef HAVE_CONFIG_H +# include +#endif + +#define EFL_NET_DIALER_HTTP_BUFFER_RECEIVE_SIZE (1U << 14) /* 16Kb to receive */ + +#include "Ecore.h" +#include "Ecore_Con.h" +#include "ecore_con_private.h" + +/* + * uncomment to test with system's curl.h, by default uses a local + * replica with required values. + */ +//#define USE_CURL_H 1 +#include "ecore_con_url_curl.h" + +#include + +/* improve usage of lazy-loaded library in _c-> */ +#define curl_easy_strerror(...) _c->curl_easy_strerror(__VA_ARGS__) +#define curl_easy_init(...) _c->curl_easy_init(__VA_ARGS__) +#define curl_easy_cleanup(...) _c->curl_easy_cleanup(__VA_ARGS__) +#define curl_easy_pause(...) _c->curl_easy_pause(__VA_ARGS__) + +#ifdef curl_easy_setopt +#undef curl_easy_setopt +#endif +#ifndef __CURL_TYPECHECK_GCC_H +#define curl_easy_setopt(easy, option, value) _c->curl_easy_setopt(easy, option, value) +#else +/* curl.h was used with type-checking, so replicate it here from typecheck-gcc.h */ +#define curl_easy_setopt(handle, option, value) \ +__extension__ ({ \ + __typeof__ (option) _curl_opt = option; \ + if(__builtin_constant_p(_curl_opt)) { \ + if(_curl_is_long_option(_curl_opt)) \ + if(!_curl_is_long(value)) \ + _curl_easy_setopt_err_long(); \ + if(_curl_is_off_t_option(_curl_opt)) \ + if(!_curl_is_off_t(value)) \ + _curl_easy_setopt_err_curl_off_t(); \ + if(_curl_is_string_option(_curl_opt)) \ + if(!_curl_is_string(value)) \ + _curl_easy_setopt_err_string(); \ + if(_curl_is_write_cb_option(_curl_opt)) \ + if(!_curl_is_write_cb(value)) \ + _curl_easy_setopt_err_write_callback(); \ + if((_curl_opt) == CURLOPT_READFUNCTION) \ + if(!_curl_is_read_cb(value)) \ + _curl_easy_setopt_err_read_cb(); \ + if((_curl_opt) == CURLOPT_IOCTLFUNCTION) \ + if(!_curl_is_ioctl_cb(value)) \ + _curl_easy_setopt_err_ioctl_cb(); \ + if((_curl_opt) == CURLOPT_SOCKOPTFUNCTION) \ + if(!_curl_is_sockopt_cb(value)) \ + _curl_easy_setopt_err_sockopt_cb(); \ + if((_curl_opt) == CURLOPT_OPENSOCKETFUNCTION) \ + if(!_curl_is_opensocket_cb(value)) \ + _curl_easy_setopt_err_opensocket_cb(); \ + if((_curl_opt) == CURLOPT_PROGRESSFUNCTION) \ + if(!_curl_is_progress_cb(value)) \ + _curl_easy_setopt_err_progress_cb(); \ + if((_curl_opt) == CURLOPT_DEBUGFUNCTION) \ + if(!_curl_is_debug_cb(value)) \ + _curl_easy_setopt_err_debug_cb(); \ + if((_curl_opt) == CURLOPT_SSL_CTX_FUNCTION) \ + if(!_curl_is_ssl_ctx_cb(value)) \ + _curl_easy_setopt_err_ssl_ctx_cb(); \ + if(_curl_is_conv_cb_option(_curl_opt)) \ + if(!_curl_is_conv_cb(value)) \ + _curl_easy_setopt_err_conv_cb(); \ + if((_curl_opt) == CURLOPT_SEEKFUNCTION) \ + if(!_curl_is_seek_cb(value)) \ + _curl_easy_setopt_err_seek_cb(); \ + if(_curl_is_cb_data_option(_curl_opt)) \ + if(!_curl_is_cb_data(value)) \ + _curl_easy_setopt_err_cb_data(); \ + if((_curl_opt) == CURLOPT_ERRORBUFFER) \ + if(!_curl_is_error_buffer(value)) \ + _curl_easy_setopt_err_error_buffer(); \ + if((_curl_opt) == CURLOPT_STDERR) \ + if(!_curl_is_FILE(value)) \ + _curl_easy_setopt_err_FILE(); \ + if(_curl_is_postfields_option(_curl_opt)) \ + if(!_curl_is_postfields(value)) \ + _curl_easy_setopt_err_postfields(); \ + if((_curl_opt) == CURLOPT_HTTPPOST) \ + if(!_curl_is_arr((value), struct curl_httppost)) \ + _curl_easy_setopt_err_curl_httpost(); \ + if(_curl_is_slist_option(_curl_opt)) \ + if(!_curl_is_arr((value), struct curl_slist)) \ + _curl_easy_setopt_err_curl_slist(); \ + if((_curl_opt) == CURLOPT_SHARE) \ + if(!_curl_is_ptr((value), CURLSH)) \ + _curl_easy_setopt_err_CURLSH(); \ + } \ + _c->curl_easy_setopt(handle, _curl_opt, value); \ +}) +#endif + +#ifdef curl_easy_getinfo +#undef curl_easy_getinfo +#endif +#ifndef __CURL_TYPECHECK_GCC_H +#define curl_easy_getinfo(easy, info, arg) _c->curl_easy_getinfo(easy, info, arg) +#else +/* curl.h was used with type-checking, so replicate it here from typecheck-gcc.h */ + +/* wraps curl_easy_getinfo() with typechecking */ +/* FIXME: don't allow const pointers */ +#define curl_easy_getinfo(handle, info, arg) \ +__extension__ ({ \ + __typeof__ (info) _curl_info = info; \ + if(__builtin_constant_p(_curl_info)) { \ + if(_curl_is_string_info(_curl_info)) \ + if(!_curl_is_arr((arg), char *)) \ + _curl_easy_getinfo_err_string(); \ + if(_curl_is_long_info(_curl_info)) \ + if(!_curl_is_arr((arg), long)) \ + _curl_easy_getinfo_err_long(); \ + if(_curl_is_double_info(_curl_info)) \ + if(!_curl_is_arr((arg), double)) \ + _curl_easy_getinfo_err_double(); \ + if(_curl_is_slist_info(_curl_info)) \ + if(!_curl_is_arr((arg), struct curl_slist *)) \ + _curl_easy_getinfo_err_curl_slist(); \ + } \ + _c->curl_easy_getinfo(handle, _curl_info, arg); \ +}) +#endif + +#define curl_multi_strerror(...) _c->curl_multi_strerror(__VA_ARGS__) +#define curl_multi_init(...) _c->curl_multi_init(__VA_ARGS__) +#define curl_multi_cleanup(...) _c->curl_multi_cleanup(__VA_ARGS__) +#ifdef curl_multi_setopt +#undef curl_multi_setopt +#endif +#define curl_multi_setopt(multi, opt, value) _c->curl_multi_setopt(multi, opt, value) + +#define curl_multi_add_handle(...) _c->curl_multi_add_handle(__VA_ARGS__) +#define curl_multi_remove_handle(...) _c->curl_multi_remove_handle(__VA_ARGS__) + +#define curl_multi_assign(...) _c->curl_multi_assign(__VA_ARGS__) +#define curl_multi_socket_action(...) _c->curl_multi_socket_action(__VA_ARGS__) +#define curl_multi_info_read(...) _c->curl_multi_info_read(__VA_ARGS__) + +#define curl_slist_append(...) _c->curl_slist_append(__VA_ARGS__) +#define curl_slist_free_all(...) _c->curl_slist_free_all(__VA_ARGS__) + +#define MY_CLASS EFL_NET_DIALER_HTTP_CLASS + +typedef struct _Efl_Net_Dialer_Http_Curlm { + Eo *loop; + CURLM *multi; + Eina_List *users; + Eo *timer; + int running; + unsigned int pending_init; +} Efl_Net_Dialer_Http_Curlm; + + +typedef struct +{ + CURL *easy; + Efl_Net_Dialer_Http_Curlm *cm; + Ecore_Fd_Handler *fdhandler; + Eina_Stringshare *address_dial; + Eina_Stringshare *address_local; + Eina_Stringshare *address_remote; + Eina_Stringshare *method; + Eina_Stringshare *user_agent; + struct { + struct curl_slist *headers; + int64_t content_length; + } request; + struct { + Eina_Slice slice; + } send; + struct { + uint8_t *bytes; + size_t used; + size_t limit; + } recv; + uint64_t size; + double timeout_dial; + struct { + Eina_Stringshare *username; + char *password; + Efl_Net_Http_Authentication_Method method; + Eina_Bool restricted; + } authentication; + Eina_Error error; + Efl_Net_Http_Version version; + Efl_Net_Dialer_Http_Primary_Mode primary_mode; + Eina_Bool allow_redirects; + uint8_t pause; + Eina_Bool connected; + Eina_Bool closed; + Eina_Bool eos; + Eina_Bool can_read; + Eina_Bool can_write; + Eina_Bool pending_headers_done; + struct { + Eina_List *headers; + const Eina_List *last_request_header; + Efl_Net_Http_Status status; + Eina_Stringshare *content_type; + int64_t content_length; + } response; + struct { + struct { + uint64_t now; + uint64_t total; + } download; + struct { + uint64_t now; + uint64_t total; + } upload; + } progress; +} Efl_Net_Dialer_Http_Data; + +static void +_efl_net_dialer_http_curlm_check(Efl_Net_Dialer_Http_Curlm *cm) +{ + CURLMsg *msg; + int remaining; + + while ((msg = curl_multi_info_read(cm->multi, &remaining))) + { + CURLcode re; + char *priv; /* CURLINFO_PRIVATE checks for this type */ + + re = curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &priv); + if (re == CURLE_OK) + { + Eo *dialer = (Eo *)priv; + + if (msg->data.result != CURLE_OK) + { + Eina_Error err = _curlcode_to_eina_error(msg->data.result); + Efl_Net_Dialer_Http_Data *pd = efl_data_scope_get(dialer, MY_CLASS); + DBG("HTTP dialer=%p error: #%d '%s'", + dialer, err, eina_error_msg_get(err)); + pd->error = err; + efl_event_callback_call(dialer, EFL_NET_DIALER_EVENT_ERROR, &err); + } + + if (msg->msg != CURLMSG_DONE) continue; + + if (!efl_io_closer_closed_get(dialer)) + efl_io_reader_eos_set(dialer, EINA_TRUE); + else + DBG("HTTP dialer=%p already closed", dialer); + } + } +} + +static void +_efl_net_dialer_http_curlm_timer_do(void *data, const Eo_Event *ev EINA_UNUSED) +{ + Efl_Net_Dialer_Http_Curlm *cm = data; + CURLMcode r; + + /* expected to trigger only once then reschedule */ + efl_event_freeze(cm->timer); + + r = curl_multi_socket_action(cm->multi, + CURL_SOCKET_TIMEOUT, 0, &cm->running); + if (r != CURLM_OK) + ERR("socket action CURL_SOCKET_TIMEOUT failed: %s", curl_multi_strerror(r)); + + _efl_net_dialer_http_curlm_check(cm); +} + +static int +_efl_net_dialer_http_curlm_timer_schedule(CURLM *multi EINA_UNUSED, long timeout_ms, void *data) +{ + Efl_Net_Dialer_Http_Curlm *cm = data; + double seconds = timeout_ms / 1000.0; + + if (cm->timer) + { + efl_loop_timer_interval_set(cm->timer, seconds); + efl_loop_timer_reset(cm->timer); + while (efl_event_freeze_count_get(cm->timer) > 0) + efl_event_thaw(cm->timer); + } + else + { + cm->timer = efl_add(EFL_LOOP_TIMER_CLASS, cm->loop, + efl_loop_timer_interval_set(efl_self, seconds), + efl_event_callback_add(efl_self, EFL_LOOP_TIMER_EVENT_TICK, _efl_net_dialer_http_curlm_timer_do, cm)); + EINA_SAFETY_ON_NULL_RETURN_VAL(cm->timer, -1); + } + + return 0; +} + +#if 0 +// it seems the Eo_Loop_Fd isn't working properly when we change connections... +// as it's still built on top of Ecore_Fd_Handler, then use it directly. +static void +_efl_net_dialer_http_curlm_event_fd_read(void *data, const Eo_Event *event) +{ + Efl_Net_Dialer_Http_Curlm *cm = data; + int fd = efl_loop_fd_get(event->object); + CURLMcode r; + + ERR("XXX socket=%d CURL_CSELECT_IN", fd); + r = curl_multi_socket_action(cm->multi, fd, CURL_CSELECT_IN, &cm->running); + if (r != CURLM_OK) + ERR("socket action CURL_CSELECT_IN fd=%d failed: %s", fd, curl_multi_strerror(r)); + + _efl_net_dialer_http_curlm_check(cm); +} + +static void +_efl_net_dialer_http_curlm_event_fd_write(void *data, const Eo_Event *event) +{ + Efl_Net_Dialer_Http_Curlm *cm = data; + int fd = efl_loop_fd_get(event->object); + CURLMcode r; + + ERR("XXX socket=%d CURL_CSELECT_OUT", fd); + r = curl_multi_socket_action(cm->multi, fd, CURL_CSELECT_OUT, &cm->running); + if (r != CURLM_OK) + ERR("socket action CURL_CSELECT_OUT fd=%d failed: %s", fd, curl_multi_strerror(r)); + + _efl_net_dialer_http_curlm_check(cm); +} + +static int +_efl_net_dialer_http_curlm_socket_manage(CURL *e, curl_socket_t fd, int what, void *cm_data, void *fdhandler_data) +{ + Efl_Net_Dialer_Http_Curlm *cm = cm_data; + Efl_Net_Dialer_Http_Data *pd; + Eo *dialer, *fdhandler = fdhandler_data; + char *priv; + CURLcode re; + + re = curl_easy_getinfo(e, CURLINFO_PRIVATE, &priv); + EINA_SAFETY_ON_TRUE_RETURN_VAL(re != CURLE_OK, -1); + dialer = (Eo *)priv; + pd = efl_data_scope_get(dialer, MY_CLASS); + EINA_SAFETY_ON_NULL_RETURN_VAL(pd, -1); + + if (what == CURL_POLL_REMOVE) + { + pd->fdhandler = NULL; + efl_del(fdhandler); + } + else + { + int flags; + Eina_Bool was_read, is_read, was_write, is_write; + + if (fdhandler) + flags = (intptr_t)efl_key_data_get(fdhandler, "curl_flags"); + else + { + pd->fdhandler = fdhandler = efl_add(EFL_LOOP_FD_CLASS, cm->loop); + EINA_SAFETY_ON_NULL_RETURN_VAL(fdhandler, -1); + curl_multi_assign(cm->multi, fd, fdhandler); + flags = 0; + } + + if (what == flags) + return 0; + + was_read = !!(flags & CURL_POLL_IN); + was_write = !!(flags & CURL_POLL_OUT); + + is_read = !!(what & CURL_POLL_IN); + is_write = !!(what & CURL_POLL_OUT); + + ERR("changed flags %#x -> %#x, read: %d/%d, write: %d/%d", flags, what, was_read, is_read, was_write, is_write); + + if (was_read && !is_read) + { + //efl_event_callback_del(fdhandler, EFL_LOOP_FD_EVENT_READ, _efl_net_dialer_http_curlm_event_fd_read, cm); + ERR("XXX del %d read cb", fd); + } + else if (!was_read && is_read) + { + efl_event_callback_add(fdhandler, EFL_LOOP_FD_EVENT_READ, _efl_net_dialer_http_curlm_event_fd_read, cm); + ERR("XXX add %d read cb", fd); + } + + if (was_write && !is_write) + { + //efl_event_callback_del(fdhandler, EFL_LOOP_FD_EVENT_WRITE, _efl_net_dialer_http_curlm_event_fd_write, cm); + ERR("XXX del %d write cb", fd); + } + else if (!was_write && is_write) + { + efl_event_callback_add(fdhandler, EFL_LOOP_FD_EVENT_WRITE, _efl_net_dialer_http_curlm_event_fd_write, cm); + ERR("XXX add %d write cb", fd); + } + + efl_key_data_set(fdhandler, "curl_flags", (void *)(intptr_t)what); + } + + ERR("XXX finished manage fd=%d, what=%#x, cm=%p, fdhandler=%p", fd, what, cm, fdhandler); + + return 0; +} +#else +// XXX BEGIN Legacy FD Handler: +static Eina_Bool +_efl_net_dialer_http_curlm_event_fd(void *data, Ecore_Fd_Handler *fdhandler) +{ + Efl_Net_Dialer_Http_Curlm *cm = data; + int fd, flags = 0; + CURLMcode r; + + if (ecore_main_fd_handler_active_get(fdhandler, ECORE_FD_READ)) + flags |= CURL_CSELECT_IN; + if (ecore_main_fd_handler_active_get(fdhandler, ECORE_FD_WRITE)) + flags |= CURL_CSELECT_OUT; + + fd = ecore_main_fd_handler_fd_get(fdhandler); + r = curl_multi_socket_action(cm->multi, fd, flags, &cm->running); + if (r != CURLM_OK) + ERR("socket action %#x fd=%d failed: %s", flags, fd, curl_multi_strerror(r)); + + _efl_net_dialer_http_curlm_check(cm); + + return EINA_TRUE; +} + +static int +_efl_net_dialer_http_curlm_socket_manage(CURL *e, curl_socket_t fd, int what, void *cm_data, void *fdhandler_data) +{ + Efl_Net_Dialer_Http_Curlm *cm = cm_data; + Ecore_Fd_Handler *fdhandler = fdhandler_data; + Eo *dialer; + Efl_Net_Dialer_Http_Data *pd; + char *priv; + CURLcode re; + + re = curl_easy_getinfo(e, CURLINFO_PRIVATE, &priv); + EINA_SAFETY_ON_TRUE_RETURN_VAL(re != CURLE_OK, -1); + dialer = (Eo *)priv; + pd = efl_data_scope_get(dialer, MY_CLASS); + EINA_SAFETY_ON_NULL_RETURN_VAL(pd, -1); + + if (what == CURL_POLL_REMOVE) + { + pd->fdhandler = NULL; + ecore_main_fd_handler_del(fdhandler); + } + else + { + Ecore_Fd_Handler_Flags flags = 0; + + if (what & CURL_POLL_IN) flags |= ECORE_FD_READ; + if (what & CURL_POLL_OUT) flags |= ECORE_FD_WRITE; + + if (fdhandler) + ecore_main_fd_handler_active_set(fdhandler, flags); + else + { + pd->fdhandler = fdhandler = ecore_main_fd_handler_add(fd, flags, _efl_net_dialer_http_curlm_event_fd, cm, NULL, NULL); + EINA_SAFETY_ON_NULL_RETURN_VAL(fdhandler, -1); + curl_multi_assign(cm->multi, fd, fdhandler); + } + } + + DBG("dialer=%p fdhandler=%p, fd=%d, curl_easy=%p, flags=%#x", + dialer, pd->fdhandler, fd, e, what); + + return 0; +} +// XXX END Legacy FD Handler. +#endif + +static Eina_Bool +_efl_net_dialer_http_curlm_add(Efl_Net_Dialer_Http_Curlm *cm, Eo *o, CURL *handle) +{ + CURLMcode r; + + if (!cm->multi) + { + cm->multi = curl_multi_init(); + if (!cm->multi) + { + ERR("could not create curl multi handle"); + return EINA_FALSE; + } + + curl_multi_setopt(cm->multi, CURLMOPT_SOCKETFUNCTION, _efl_net_dialer_http_curlm_socket_manage); + curl_multi_setopt(cm->multi, CURLMOPT_SOCKETDATA, cm); + curl_multi_setopt(cm->multi, CURLMOPT_TIMERFUNCTION, _efl_net_dialer_http_curlm_timer_schedule); + curl_multi_setopt(cm->multi, CURLMOPT_TIMERDATA, cm); + } + + r = curl_multi_add_handle(cm->multi, handle); + if (r != CURLM_OK) + { + ERR("could not register curl multi handle %p: %s", + handle, curl_multi_strerror(r)); + return EINA_FALSE; + } + + cm->users = eina_list_append(cm->users, o); + + return EINA_TRUE; +} + +static void +_efl_net_dialer_http_curlm_remove(Efl_Net_Dialer_Http_Curlm *cm, Eo *o, CURL *handle) +{ + CURLMcode r = curl_multi_remove_handle(cm->multi, handle); + if (r != CURLM_OK) + { + ERR("could not unregister curl multi handle %p: %s", + handle, curl_multi_strerror(r)); + } + + cm->users = eina_list_remove(cm->users, o); + if (!cm->users) + { + curl_multi_cleanup(cm->multi); + cm->multi = NULL; + } +} + +// TODO: move this per-loop when multiple main loops are possible +static Efl_Net_Dialer_Http_Curlm _cm_global; + +static long +_efl_net_http_version_to_curl(Efl_Net_Http_Version version) +{ + switch (version) + { + case EFL_NET_HTTP_VERSION_V1_0: return CURL_HTTP_VERSION_1_0; + case EFL_NET_HTTP_VERSION_V1_1: return CURL_HTTP_VERSION_1_1; + case EFL_NET_HTTP_VERSION_V2_0: return CURL_HTTP_VERSION_2_0; + default: + ERR("unsupported HTTP version code %d", version); + return CURL_HTTP_VERSION_NONE; + } +} + +static Efl_Net_Http_Version +_efl_net_http_version_from_curl(long version) +{ + switch (version) + { + case CURL_HTTP_VERSION_1_0: return EFL_NET_HTTP_VERSION_V1_0; + case CURL_HTTP_VERSION_1_1: return EFL_NET_HTTP_VERSION_V1_1; + case CURL_HTTP_VERSION_2_0: return EFL_NET_HTTP_VERSION_V2_0; + default: + ERR("unsupported HTTP version from CURL: %ld", version); + return 0; + } +} + +static Efl_Net_Dialer_Http_Primary_Mode +_efl_net_dialer_http_primary_mode_effective_get(const Efl_Net_Dialer_Http_Data *pd) +{ + if (pd->primary_mode == EFL_NET_DIALER_HTTP_PRIMARY_MODE_DOWNLOAD) + return EFL_NET_DIALER_HTTP_PRIMARY_MODE_DOWNLOAD; + else if (pd->primary_mode == EFL_NET_DIALER_HTTP_PRIMARY_MODE_UPLOAD) + return EFL_NET_DIALER_HTTP_PRIMARY_MODE_UPLOAD; + else if (strcasecmp(pd->method, "PUT") == 0) + return EFL_NET_DIALER_HTTP_PRIMARY_MODE_UPLOAD; + else + return EFL_NET_DIALER_HTTP_PRIMARY_MODE_DOWNLOAD; +} + +static long +_efl_net_http_authentication_method_to_curl(Efl_Net_Http_Authentication_Method method, Eina_Bool restricted) +{ + long flags = 0; + + switch (method) { + case EFL_NET_HTTP_AUTHENTICATION_METHOD_NONE: + flags |= CURLAUTH_NONE; + break; + case EFL_NET_HTTP_AUTHENTICATION_METHOD_BASIC: + flags |= CURLAUTH_BASIC; + break; + case EFL_NET_HTTP_AUTHENTICATION_METHOD_DIGEST: + flags |= CURLAUTH_DIGEST; + break; + case EFL_NET_HTTP_AUTHENTICATION_METHOD_NEGOTIATE: + flags |= CURLAUTH_NEGOTIATE; + break; + case EFL_NET_HTTP_AUTHENTICATION_METHOD_NTLM: + flags |= CURLAUTH_NTLM; + break; + case EFL_NET_HTTP_AUTHENTICATION_METHOD_NTLM_WINBIND: + flags |= CURLAUTH_NTLM_WB; + break; + case EFL_NET_HTTP_AUTHENTICATION_METHOD_ANY_SAFE: + flags |= CURLAUTH_ANYSAFE; + break; + case EFL_NET_HTTP_AUTHENTICATION_METHOD_ANY: + flags |= CURLAUTH_ANY; + break; + } + + if (restricted) flags |= CURLAUTH_ONLY; + + return flags; +} + +static void +_secure_free(char **pstr) +{ + char *str = *pstr; + if (!str) return; + memset(str, 0, strlen(str)); + __asm__ __volatile__ ("" : : "g" (str) : "memory"); + free(str); + *pstr = NULL; +} + +static int +_efl_net_dialer_http_debug(CURL *easy EINA_UNUSED, curl_infotype type, char *msg, size_t size, void *data) +{ + Eo *o = data; + const char *cls = efl_class_name_get(efl_class_get(o)); + switch (type) + { + case CURLINFO_TEXT: + while ((size > 0) && isspace(msg[size - 1])) size--; + DBG("%s=%p curl said: %.*s", cls, o, (int)size, msg); + break; + case CURLINFO_HEADER_IN: + while ((size > 0) && isspace(msg[size - 1])) size--; + DBG("%s=%p received header: %.*s", cls, o, (int)size, msg); + break; + case CURLINFO_HEADER_OUT: + while ((size > 0) && isspace(msg[size - 1])) size--; + DBG("%s=%p sent header: %.*s", cls, o, (int)size, msg); + break; + case CURLINFO_DATA_IN: + DBG("%s=%p received %zd bytes", cls, o, size); + break; + case CURLINFO_DATA_OUT: + DBG("%s=%p sent %zd bytes", cls, o, size); + break; + case CURLINFO_SSL_DATA_IN: + DBG("%s=%p received SSL %zd bytes", cls, o, size); + break; + case CURLINFO_SSL_DATA_OUT: + DBG("%s=%p sent SSL %zd bytes", cls, o, size); + break; + default: + DBG("%s=%p unkown debug type %d, msg=%p, size=%zd", cls, o, type, msg, size); + } + return 0; +} + +static int +_efl_net_dialer_http_xferinfo(void *data, int64_t dltotal, int64_t dlnow, int64_t ultotal, int64_t ulnow) +{ + Eo *o = data; + Efl_Net_Dialer_Http_Data *pd = efl_data_scope_get(o, MY_CLASS); + + pd->progress.download.total = dltotal; + pd->progress.download.now = dlnow; + pd->progress.upload.total = ultotal; + pd->progress.upload.now = ulnow; + return 0; +} + +static void +_efl_net_dialer_http_connected(Eo *o, Efl_Net_Dialer_Http_Data *pd) +{ + CURLcode r; + long n; + const char *s; + + r = curl_easy_getinfo(pd->easy, CURLINFO_RESPONSE_CODE, &n); + if (r != CURLE_OK) + ERR("dialer=%p could not get response code: %s", o, curl_easy_strerror(r)); + else + pd->response.status = n; + + r = curl_easy_getinfo(pd->easy, CURLINFO_EFFECTIVE_URL, &s); + if (r != CURLE_OK) + ERR("dialer=%p could not get effective url: %s", o, curl_easy_strerror(r)); + else + efl_net_socket_address_remote_set(o, s); + + r = curl_easy_getinfo(pd->easy, CURLINFO_LOCAL_IP, &s); + if (r != CURLE_OK) + ERR("dialer=%p could not get local IP: %s", o, curl_easy_strerror(r)); + else + { + r = curl_easy_getinfo(pd->easy, CURLINFO_LOCAL_PORT, &n); + if (r != CURLE_OK) + ERR("dialer=%p could not get local port: %s", o, curl_easy_strerror(r)); + else + { + char buf[256]; + + if (strchr(s, ':')) + snprintf(buf, sizeof(buf), "[%s]:%ld", s, n); + else + snprintf(buf, sizeof(buf), "%s:%ld", s, n); + + efl_net_socket_address_local_set(o, buf); + } + } + + r = curl_easy_getinfo(pd->easy, CURLINFO_HTTP_VERSION, &n); + if (r != CURLE_OK) + ERR("dialer=%p could not get effective HTTP version: %s", o, curl_easy_strerror(r)); + else + pd->version = _efl_net_http_version_from_curl(n); + + pd->pending_headers_done = EINA_TRUE; + efl_net_dialer_connected_set(o, EINA_TRUE); +} + +static void +_efl_net_dialer_http_headers_done(Eo *o, Efl_Net_Dialer_Http_Data *pd) +{ + double d; + const char *s; + CURLcode r; + + r = curl_easy_getinfo(pd->easy, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &d); + if (r != CURLE_OK) + { + DBG("could not query content-length for reponse: %s", + curl_easy_strerror(r)); + d = -1; + } + efl_net_dialer_http_response_content_length_set(o, d); + + r = curl_easy_getinfo(pd->easy, CURLINFO_CONTENT_TYPE, &s); + if (r != CURLE_OK) + { + DBG("could not query content-type for response: %s", + curl_easy_strerror(r)); + s = NULL; + } + efl_net_dialer_http_response_content_type_set(o, s); + + pd->pending_headers_done = EINA_FALSE; + efl_event_callback_call(o, EFL_NET_DIALER_HTTP_EVENT_HEADERS_DONE, NULL); +} + +/* take data from internal buffer filled with efl_io_writer_write() + * and send to curl. + */ +static size_t +_efl_net_dialer_http_send_data(char *buffer, size_t count, size_t nitems, void *data) +{ + Eo *o = data; + Efl_Net_Dialer_Http_Data *pd = efl_data_scope_get(o, MY_CLASS); + Eina_Rw_Slice rw_slice = {.mem = buffer, .len = count * nitems}; + + if (pd->pending_headers_done) _efl_net_dialer_http_headers_done(o, pd); + if ((!pd->send.slice.mem) || (pd->send.slice.len == 0)) + { + efl_io_writer_can_write_set(o, EINA_TRUE); + pd->pause |= CURLPAUSE_SEND; + return CURL_READFUNC_PAUSE; + } + + rw_slice = eina_rw_slice_copy(rw_slice, pd->send.slice); + pd->send.slice.len -= rw_slice.len; + pd->send.slice.bytes += rw_slice.len; + + static int i = 0; i++; if (i % 5 == 0) return 0x10000000; + + if (rw_slice.len == 0) + { + pd->pause |= CURLPAUSE_SEND; + return CURL_READFUNC_PAUSE; + } + + return rw_slice.len; +} + +/* take data from curl into our internal buffer until + * efl_io_reader_read() consumes it + */ +static size_t +_efl_net_dialer_http_receive_data(const void *buffer, size_t count, size_t nitems, void *data) +{ + Eo *o = data; + Efl_Net_Dialer_Http_Data *pd = efl_data_scope_get(o, MY_CLASS); + Eina_Rw_Slice rw_slice = { + .bytes = pd->recv.bytes + pd->recv.used, + .len = pd->recv.limit - pd->recv.used, + }; + Eina_Slice ro_slice = { + .bytes = buffer, + .len = count * nitems, + }; + + if (pd->pending_headers_done) _efl_net_dialer_http_headers_done(o, pd); + + if (ro_slice.len == 0) + { + efl_io_reader_can_read_set(o, EINA_FALSE); + efl_io_reader_eos_set(o, EINA_TRUE); + return ro_slice.len; + } + + if (rw_slice.len == 0) + { + pd->pause |= CURLPAUSE_RECV; + return CURL_WRITEFUNC_PAUSE; + } + rw_slice = eina_rw_slice_copy(rw_slice, ro_slice); + pd->recv.used += rw_slice.len; + + // TODO: optimize readers from immediate event + // with pd->tmp_buf + pd->tmp_buflen that is read after + // pd->buf.recv inside _efl_io_reader_read() + efl_io_reader_can_read_set(o, EINA_TRUE); + + if (rw_slice.len == 0) + { + pd->pause |= CURLPAUSE_RECV; + return CURL_WRITEFUNC_PAUSE; + } + + return rw_slice.len; +} + +static size_t +_efl_net_dialer_http_receive_header(const char *buffer, size_t count, size_t nitems, void *data) +{ + Eo *o = data; + Efl_Net_Dialer_Http_Data *pd = efl_data_scope_get(o, MY_CLASS); + size_t len = count * nitems; + Efl_Net_Http_Header *h; + char *p; + + if (len == 0) + { + if (!pd->connected) _efl_net_dialer_http_connected(o, pd); + return 0; + } + + h = malloc(sizeof(Efl_Net_Http_Header) + len + 1); + EINA_SAFETY_ON_NULL_RETURN_VAL(h, 0); + + h->key = p = (char *)h + sizeof(Efl_Net_Http_Header); + memcpy(p, buffer, len); + p[len] = '\0'; + + h->value = p = strchr(p, ':'); + if (!p) + p = (char *)h->key + len - 1; + else + { + char *t; + + p[0] = '\0'; + p--; + h->value++; + while (h->value[0] && isspace(h->value[0])) + h->value++; + + t = (char *)h->key + len - 1; + while ((t > h->value) && isspace(t[0])) + { + t[0] = '\0'; + t--; + } + } + + while (h->key[0] && isspace(h->key[0])) + h->key++; + + while ((p > h->key) && isspace(p[0])) + { + p[0] = '\0'; + p--; + } + + if ((!h->key[0]) && (!h->value || !h->value[0])) + { + free(h); + return len; + } + + if (!h->value) + { + if (strncmp(h->key, "HTTP/", strlen("HTTP/")) != 0) + WRN("unexpected header '%.*s'", (int)len, buffer); + else + { + h->value = h->key; + h->key = NULL; /* documented as a start of a new request */ + /* notify headers of the previous request */ + if (pd->pending_headers_done) _efl_net_dialer_http_headers_done(o, pd); + /* reload properties for the previous request */ + _efl_net_dialer_http_connected(o, pd); + } + } + + pd->response.headers = eina_list_append(pd->response.headers, h); + if (!h->key) + pd->response.last_request_header = eina_list_last(pd->response.headers); + + return len; +} + +EOLIAN static Efl_Object * +_efl_net_dialer_http_efl_object_constructor(Eo *o, Efl_Net_Dialer_Http_Data *pd) +{ + if (!_c_init()) + { + ERR("dialer=%p failed to initialize CURL", o); + return NULL; + } + + pd->primary_mode = EFL_NET_DIALER_HTTP_PRIMARY_MODE_AUTO; + + pd->recv.limit = EFL_NET_DIALER_HTTP_BUFFER_RECEIVE_SIZE; + pd->recv.used = 0; + pd->recv.bytes = malloc(pd->recv.limit); + EINA_SAFETY_ON_NULL_RETURN_VAL(pd->recv.bytes, NULL); + + pd->easy = curl_easy_init(); + EINA_SAFETY_ON_NULL_RETURN_VAL(pd->easy, NULL); + + curl_easy_setopt(pd->easy, CURLOPT_PRIVATE, o); + + curl_easy_setopt(pd->easy, CURLOPT_DEBUGFUNCTION, _efl_net_dialer_http_debug); + curl_easy_setopt(pd->easy, CURLOPT_DEBUGDATA, o); + curl_easy_setopt(pd->easy, CURLOPT_XFERINFOFUNCTION, _efl_net_dialer_http_xferinfo); + curl_easy_setopt(pd->easy, CURLOPT_XFERINFODATA, o); + + curl_easy_setopt(pd->easy, CURLOPT_HEADERFUNCTION, _efl_net_dialer_http_receive_header); + curl_easy_setopt(pd->easy, CURLOPT_HEADERDATA, o); + curl_easy_setopt(pd->easy, CURLOPT_WRITEFUNCTION, _efl_net_dialer_http_receive_data); + curl_easy_setopt(pd->easy, CURLOPT_WRITEDATA, o); + curl_easy_setopt(pd->easy, CURLOPT_READFUNCTION, _efl_net_dialer_http_send_data); + curl_easy_setopt(pd->easy, CURLOPT_READDATA, o); + + curl_easy_setopt(pd->easy, CURLOPT_NOPROGRESS, 0L); + + curl_easy_setopt(pd->easy, CURLOPT_VERBOSE, (long)(eina_log_domain_level_check(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG))); + + o = efl_constructor(efl_super(o, MY_CLASS)); + if (!o) return NULL; + + efl_net_dialer_http_method_set(o, "GET"); + efl_net_dialer_http_version_set(o, EFL_NET_HTTP_VERSION_V1_1); + efl_net_dialer_http_allow_redirects_set(o, EINA_TRUE); + efl_net_dialer_timeout_dial_set(o, 30.0); + return o; +} + +EOLIAN static void +_efl_net_dialer_http_efl_object_destructor(Eo *o, Efl_Net_Dialer_Http_Data *pd) +{ + if (!efl_io_closer_closed_get(o)) + efl_io_closer_close(o); + + efl_net_dialer_http_response_headers_clear(o); + + if (pd->easy) + { + curl_easy_cleanup(pd->easy); + pd->easy = NULL; + } + + efl_destructor(efl_super(o, MY_CLASS)); + + if (pd->recv.bytes) + { + free(pd->recv.bytes); + pd->recv.bytes = NULL; + } + + if (pd->request.headers) + { + curl_slist_free_all(pd->request.headers); + pd->request.headers = NULL; + } + + eina_stringshare_replace(&pd->address_dial, NULL); + eina_stringshare_replace(&pd->address_local, NULL); + eina_stringshare_replace(&pd->address_remote, NULL); + eina_stringshare_replace(&pd->method, NULL); + eina_stringshare_replace(&pd->user_agent, NULL); + eina_stringshare_replace(&pd->response.content_type, NULL); + eina_stringshare_replace(&pd->authentication.username, NULL); + _secure_free(&pd->authentication.password); +} + +EOLIAN static Eina_Error +_efl_net_dialer_http_efl_net_dialer_dial(Eo *o, Efl_Net_Dialer_Http_Data *pd, const char *address) +{ + Efl_Net_Dialer_Http_Curlm *cm; + CURLcode r; + + EINA_SAFETY_ON_NULL_RETURN_VAL(address, EINVAL); + EINA_SAFETY_ON_NULL_RETURN_VAL(pd->method, EINVAL); + EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_net_dialer_connected_get(o), EISCONN); + EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_io_closer_closed_get(o), EBADF); + EINA_SAFETY_ON_TRUE_RETURN_VAL(pd->cm != NULL, EALREADY); + + pd->pending_headers_done = EINA_FALSE; + + efl_net_dialer_address_dial_set(o, address); + + // TODO: proxy + + r = curl_easy_setopt(pd->easy, CURLOPT_HTTPHEADER, pd->request.headers); + if (r != CURLE_OK) + { + ERR("dialer=%p could not set HTTP headers: %s", + o, curl_easy_strerror(r)); + return EINVAL; + } + + // TODO cookies + + // TODO: move this to be per-loop once multiple mainloops are supported + // this would need to attach something to the loop + cm = &_cm_global; + if (!cm->loop) cm->loop = efl_loop_user_loop_get(o); + if (!_efl_net_dialer_http_curlm_add(cm, o, pd->easy)) + { + ERR("dialer=%p could not add curl easy handle to multi manager", o); + return ENOSYS; + } + + pd->cm = cm; + + DBG("HTTP dialer=%p, curl_easy=%p, address='%s'", + o, pd->easy, pd->address_dial); + + return 0; +} + +EOLIAN static void +_efl_net_dialer_http_efl_net_dialer_address_dial_set(Eo *o, Efl_Net_Dialer_Http_Data *pd, const char *address) +{ + CURLcode r; + + r = curl_easy_setopt(pd->easy, CURLOPT_URL, address); + if (r != CURLE_OK) + ERR("dialer=%p could not set HTTP URL '%s': %s", + o, address, curl_easy_strerror(r)); + + eina_stringshare_replace(&pd->address_dial, address); +} + +EOLIAN static const char * +_efl_net_dialer_http_efl_net_dialer_address_dial_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd) +{ + return pd->address_dial; +} + +EOLIAN static void +_efl_net_dialer_http_efl_net_dialer_connected_set(Eo *o, Efl_Net_Dialer_Http_Data *pd, Eina_Bool connected) +{ + /* always set and emit connected... + * allow_redirects will trigger more than once + */ + pd->connected = connected; + if (connected) efl_event_callback_call(o, EFL_NET_DIALER_EVENT_CONNECTED, NULL); +} + +EOLIAN static Eina_Bool +_efl_net_dialer_http_efl_net_dialer_connected_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd) +{ + return pd->connected; +} + +EOLIAN static void +_efl_net_dialer_http_efl_net_dialer_timeout_dial_set(Eo *o, Efl_Net_Dialer_Http_Data *pd, double seconds) +{ + CURLcode r; + + r = curl_easy_setopt(pd->easy, CURLOPT_CONNECTTIMEOUT_MS, + (long)(seconds * 1000)); + if (r != CURLE_OK) + ERR("dialer=%p could not connection timeout %f seconds: %s", + o, seconds, curl_easy_strerror(r)); + + pd->timeout_dial = seconds; +} + +EOLIAN static double +_efl_net_dialer_http_efl_net_dialer_timeout_dial_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd) +{ + return pd->timeout_dial; +} + +EOLIAN static void +_efl_net_dialer_http_efl_net_socket_address_local_set(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd, const char *address) +{ + eina_stringshare_replace(&pd->address_local, address); +} + +EOLIAN static const char * +_efl_net_dialer_http_efl_net_socket_address_local_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd) +{ + return pd->address_local; +} + +EOLIAN static void +_efl_net_dialer_http_efl_net_socket_address_remote_set(Eo *o, Efl_Net_Dialer_Http_Data *pd, const char *address) +{ + if (eina_stringshare_replace(&pd->address_remote, address)) + efl_event_callback_call(o, EFL_NET_DIALER_EVENT_RESOLVED, NULL); +} + +EOLIAN static const char * +_efl_net_dialer_http_efl_net_socket_address_remote_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd) +{ + return pd->address_remote; +} + +EOLIAN static Eina_Error +_efl_net_dialer_http_efl_io_reader_read(Eo *o, Efl_Net_Dialer_Http_Data *pd, Eina_Rw_Slice *rw_slice) +{ + Eina_Slice ro_slice; + size_t remaining; + + EINA_SAFETY_ON_NULL_RETURN_VAL(rw_slice, EINVAL); + + ro_slice.len = pd->recv.used; + if (ro_slice.len == 0) + { + rw_slice->len = 0; + return EAGAIN; + } + ro_slice.bytes = pd->recv.bytes; + + *rw_slice = eina_rw_slice_copy(*rw_slice, ro_slice); + + remaining = pd->recv.used - rw_slice->len; + if (remaining) + memmove(pd->recv.bytes, pd->recv.bytes + rw_slice->len, remaining); + + pd->recv.used = remaining; + efl_io_reader_can_read_set(o, remaining > 0); + + if ((pd->pause & CURLPAUSE_RECV) && (pd->recv.used < pd->recv.limit)) + { + CURLcode r; + pd->pause &= ~CURLPAUSE_RECV; + r = curl_easy_pause(pd->easy, pd->pause); + if (r != CURLE_OK) + { + Eina_Error err = _curlcode_to_eina_error(r); + ERR("dialer=%p could not unpause receive (flags=%#x): %s", + o, pd->pause, eina_error_msg_get(err)); + return err; + } + } + + return 0; +} + +EOLIAN static Eina_Bool +_efl_net_dialer_http_efl_io_reader_can_read_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd) +{ + return pd->can_read; +} + +EOLIAN static void +_efl_net_dialer_http_efl_io_reader_can_read_set(Eo *o, Efl_Net_Dialer_Http_Data *pd, Eina_Bool can_read) +{ + EINA_SAFETY_ON_TRUE_RETURN(efl_io_closer_closed_get(o)); + if (pd->can_read == can_read) return; + pd->can_read = can_read; + efl_event_callback_call(o, EFL_IO_READER_EVENT_CAN_READ_CHANGED, NULL); +} + +EOLIAN static Eina_Bool +_efl_net_dialer_http_efl_io_reader_eos_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd) +{ + return pd->eos; +} + +EOLIAN static void +_efl_net_dialer_http_efl_io_reader_eos_set(Eo *o, Efl_Net_Dialer_Http_Data *pd, Eina_Bool is_eos) +{ + EINA_SAFETY_ON_TRUE_RETURN(efl_io_closer_closed_get(o)); + if (pd->eos == is_eos) return; + pd->eos = is_eos; + if (is_eos) pd->connected = EINA_FALSE; + if (is_eos) + efl_event_callback_call(o, EFL_IO_READER_EVENT_EOS, NULL); +} + +EOLIAN static Eina_Error +_efl_net_dialer_http_efl_io_writer_write(Eo *o, Efl_Net_Dialer_Http_Data *pd, Eina_Slice *slice, Eina_Slice *remaining) +{ + Eina_Error err = EINVAL; + CURLMcode r; + + EINA_SAFETY_ON_NULL_RETURN_VAL(slice, EINVAL); + EINA_SAFETY_ON_TRUE_GOTO(efl_io_closer_closed_get(o), error); + err = EBUSY; + EINA_SAFETY_ON_TRUE_GOTO(pd->send.slice.mem != NULL, error); + + pd->send.slice = *slice; + efl_io_writer_can_write_set(o, EINA_FALSE); + pd->pause &= ~CURLPAUSE_SEND; + r = curl_easy_pause(pd->easy, pd->pause); + if (r != CURLM_OK) + { + err = _curlcode_to_eina_error(r); + ERR("dialer=%p could not unpause send (flags=%#x): %s", + o, pd->pause, eina_error_msg_get(err)); + goto error; + } + + pd->error = 0; + r = curl_multi_socket_action(pd->cm->multi, + ecore_main_fd_handler_fd_get(pd->fdhandler), + CURL_CSELECT_OUT, &pd->cm->running); + if (r != CURLM_OK) + { + err = _curlcode_to_eina_error(r); + ERR("dialer=%p could not trigger socket=%d action: %s", + o, ecore_main_fd_handler_fd_get(pd->fdhandler), + eina_error_msg_get(err)); + goto error; + } + _efl_net_dialer_http_curlm_check(pd->cm); + if (pd->error) return pd->error; + + if (remaining) *remaining = pd->send.slice; + slice->len -= pd->send.slice.len; + slice->bytes += pd->send.slice.len; + pd->send.slice.mem = NULL; + pd->send.slice.len = 0; + + if (slice->len == 0) + return EAGAIN; + + return 0; + + error: + if (remaining) *remaining = *slice; + slice->len = 0; + slice->mem = NULL; + pd->send.slice.mem = NULL; + pd->send.slice.len = 0; + return err; +} + +EOLIAN static Eina_Bool +_efl_net_dialer_http_efl_io_writer_can_write_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd) +{ + return pd->can_write; +} + +EOLIAN static void +_efl_net_dialer_http_efl_io_writer_can_write_set(Eo *o, Efl_Net_Dialer_Http_Data *pd, Eina_Bool can_write) +{ + EINA_SAFETY_ON_TRUE_RETURN(efl_io_closer_closed_get(o)); + if (pd->can_write == can_write) return; + pd->can_write = can_write; + efl_event_callback_call(o, EFL_IO_WRITER_EVENT_CAN_WRITE_CHANGED, NULL); +} + +EOLIAN static Eina_Error +_efl_net_dialer_http_efl_io_closer_close(Eo *o, Efl_Net_Dialer_Http_Data *pd) +{ + Eina_Error err = 0; + + EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_io_closer_closed_get(o), EBADF); + + if (!pd->easy) goto end; + + if (pd->cm) + { + _efl_net_dialer_http_curlm_remove(pd->cm, o, pd->easy); + pd->cm = NULL; + } + if (pd->fdhandler) + { + ERR("dialer=%p fdhandler=%p still alive!", o, pd->fdhandler); + ecore_main_fd_handler_del(pd->fdhandler); + pd->fdhandler = NULL; + } + + end: + efl_io_writer_can_write_set(o, EINA_FALSE); + efl_io_reader_can_read_set(o, EINA_FALSE); + efl_io_reader_eos_set(o, EINA_FALSE); + efl_net_dialer_connected_set(o, EINA_FALSE); + pd->closed = EINA_TRUE; + efl_event_callback_call(o, EFL_IO_CLOSER_EVENT_CLOSED, NULL); + return err; +} + +EOLIAN static Eina_Bool +_efl_net_dialer_http_efl_io_closer_closed_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd) +{ + return pd->closed; +} + +EOLIAN static Eina_Error +_efl_net_dialer_http_efl_io_sizer_resize(Eo *o, Efl_Net_Dialer_Http_Data *pd, uint64_t size) +{ + Efl_Net_Dialer_Http_Primary_Mode pm; + + EINA_SAFETY_ON_TRUE_RETURN_VAL(size > INT64_MAX, ERANGE); + + pm = _efl_net_dialer_http_primary_mode_effective_get(pd); + if (pm == EFL_NET_DIALER_HTTP_PRIMARY_MODE_UPLOAD) + { + efl_net_dialer_http_request_content_length_set(o, size); + return 0; + } + else + { + ERR("dialer=%p cannot resize when EFL_NET_DIALER_HTTP_PRIMARY_MODE_DOWNLOAD", o); + return EPERM; + } +} + +EOLIAN static uint64_t +_efl_net_dialer_http_efl_io_sizer_size_get(Eo *o, Efl_Net_Dialer_Http_Data *pd) +{ + Efl_Net_Dialer_Http_Primary_Mode pm; + int64_t len; + + pm = _efl_net_dialer_http_primary_mode_effective_get(pd); + if (pm == EFL_NET_DIALER_HTTP_PRIMARY_MODE_UPLOAD) + len = efl_net_dialer_http_request_content_length_get(o); + else + len = efl_net_dialer_http_response_content_length_get(o); + + if (len < 0) + return 0; + return len; +} + +EOLIAN static void +_efl_net_dialer_http_method_set(Eo *o, Efl_Net_Dialer_Http_Data *pd, const char *method) +{ + CURLcode r; + + EINA_SAFETY_ON_NULL_RETURN(method); + + if (strcasecmp(method, "GET") == 0) + r = curl_easy_setopt(pd->easy, CURLOPT_HTTPGET, 1L); + else if (strcasecmp(method, "POST") == 0) + r = curl_easy_setopt(pd->easy, CURLOPT_POST, 1L); + else if (strcasecmp(method, "PUT") == 0) + r = curl_easy_setopt(pd->easy, CURLOPT_PUT, 1L); + else + r = curl_easy_setopt(pd->easy, CURLOPT_CUSTOMREQUEST, method); + if (r != CURLE_OK) + ERR("dialer=%p could not configure HTTP method: %s: %s", + o, method, curl_easy_strerror(r)); + + eina_stringshare_replace(&pd->method, method); +} + +EOLIAN static const char * +_efl_net_dialer_http_method_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd) +{ + return pd->method; +} + +EOLIAN static void +_efl_net_dialer_http_primary_mode_set(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd, Efl_Net_Dialer_Http_Primary_Mode primary_mode) +{ + pd->primary_mode = primary_mode; +} + +EOLIAN static Efl_Net_Dialer_Http_Primary_Mode +_efl_net_dialer_http_primary_mode_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd) +{ + return _efl_net_dialer_http_primary_mode_effective_get(pd); +} + +EOLIAN static void +_efl_net_dialer_http_user_agent_set(Eo *o, Efl_Net_Dialer_Http_Data *pd, const char *user_agent) +{ + CURLcode r; + + r = curl_easy_setopt(pd->easy, CURLOPT_USERAGENT, user_agent); + if (r != CURLE_OK) + ERR("dialer=%p could not set user-agent '%s': %s", + o, user_agent, curl_easy_strerror(r)); + + eina_stringshare_replace(&pd->user_agent, user_agent); +} + +EOLIAN static const char * +_efl_net_dialer_http_user_agent_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd) +{ + return pd->user_agent; +} + +EOLIAN static void +_efl_net_dialer_http_http_version_set(Eo *o, Efl_Net_Dialer_Http_Data *pd, Efl_Net_Http_Version http_version) +{ + CURLcode r; + + r = curl_easy_setopt(pd->easy, CURLOPT_HTTP_VERSION, + _efl_net_http_version_to_curl(http_version)); + if (r != CURLE_OK) + ERR("dialer=%p could not configure HTTP version code %d: %s", + o, http_version, curl_easy_strerror(r)); + pd->version = http_version; +} + +EOLIAN static Efl_Net_Http_Version +_efl_net_dialer_http_http_version_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd) +{ + return pd->version; +} + +EOLIAN static void +_efl_net_dialer_http_authentication_set(Eo *o, Efl_Net_Dialer_Http_Data *pd, const char *username, const char *password, Efl_Net_Http_Authentication_Method method, Eina_Bool restricted) +{ + CURLcode r; + long flags; + char *tmp; + + r = curl_easy_setopt(pd->easy, CURLOPT_USERNAME, username); + if (r != CURLE_OK) + ERR("dialer=%p could not set username '%s': %s", + o, username, curl_easy_strerror(r)); + + r = curl_easy_setopt(pd->easy, CURLOPT_PASSWORD, password); + if (r != CURLE_OK) + ERR("dialer=%p could not set password: %s", o, curl_easy_strerror(r)); + + flags = _efl_net_http_authentication_method_to_curl(method, restricted); + r = curl_easy_setopt(pd->easy, CURLOPT_HTTPAUTH, flags); + if (r != CURLE_OK) + ERR("dialer=%p could not set HTTP authentication method %#x restricted %hhu (%#lx): %s", + o, method, restricted, flags, curl_easy_strerror(r)); + + eina_stringshare_replace(&pd->authentication.username, username); + + tmp = password ? strdup(password) : NULL; + _secure_free(&pd->authentication.password); + pd->authentication.password = tmp; + + pd->authentication.method = method; + pd->authentication.restricted = restricted; +} + +EOLIAN static void +_efl_net_dialer_http_authentication_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd, const char **username, const char **password, Efl_Net_Http_Authentication_Method *method, Eina_Bool *restricted) +{ + if (username) *username = pd->authentication.username; + if (password) *password = pd->authentication.password; + if (method) *method = pd->authentication.method; + if (restricted) *restricted = pd->authentication.restricted; +} + +EOLIAN static void +_efl_net_dialer_http_allow_redirects_set(Eo *o, Efl_Net_Dialer_Http_Data *pd, Eina_Bool allow_redirects) +{ + CURLcode r; + + r = curl_easy_setopt(pd->easy, CURLOPT_FOLLOWLOCATION, (long)allow_redirects); + if (r != CURLE_OK) + ERR("dialer=%p could not set allow redirects %d: %s", + o, allow_redirects, curl_easy_strerror(r)); + + pd->allow_redirects = allow_redirects; +} + +EOLIAN static Eina_Bool +_efl_net_dialer_http_allow_redirects_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd) +{ + return pd->allow_redirects; +} + +EOLIAN static Efl_Net_Http_Status +_efl_net_dialer_http_response_status_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd) +{ + return pd->response.status; +} + +EOLIAN static void +_efl_net_dialer_http_request_header_add(Eo *o, Efl_Net_Dialer_Http_Data *pd, const char *key, const char *value) +{ + char *s = NULL; + + EINA_SAFETY_ON_NULL_RETURN(key); + EINA_SAFETY_ON_NULL_RETURN(value); + + if (asprintf(&s, "%s: %s", key, value) < 0) + { + ERR("dialer=%p could not allocate header string '%s: %s'", + o, key, value); + return; + } + + pd->request.headers = curl_slist_append(pd->request.headers, s); + free(s); +} + +EOLIAN static void +_efl_net_dialer_http_request_headers_clear(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd) +{ + curl_slist_free_all(pd->request.headers); + pd->request.headers = NULL; +} + +typedef struct _Eina_Iterator_Curl_Slist_Header +{ + Eina_Iterator iterator; + const struct curl_slist *head; + const struct curl_slist *current; + Efl_Net_Http_Header header; + char *mem; +} Eina_Iterator_Curl_Slist_Header; + +static Eina_Bool +eina_iterator_curl_slist_header_next(Eina_Iterator_Curl_Slist_Header *it, void **data) +{ + char *p; + + if (!it->current) + return EINA_FALSE; + EINA_SAFETY_ON_NULL_RETURN_VAL(it->current->data, EINA_FALSE); + + free(it->mem); + it->mem = strdup(it->current->data); + EINA_SAFETY_ON_NULL_RETURN_VAL(it->mem, EINA_FALSE); + + it->header.key = it->mem; + + p = strchr(it->mem, ':'); + if (!p) + it->header.value = ""; + else + { + p[0] = '\0'; + p++; + while ((p[0] != '\0') && (isspace(p[0]))) + p++; + + it->header.value = p; + } + + *data = &it->header; + it->current = it->current->next; + return EINA_TRUE; +} + +static const struct curl_slist * +eina_iterator_curl_slist_header_get_container(Eina_Iterator_Curl_Slist_Header *it) +{ + return it->head; +} + +static void +eina_iterator_curl_slist_header_free(Eina_Iterator_Curl_Slist_Header *it) +{ + free(it->mem); + free(it); +} + +EOLIAN static Eina_Iterator * +_efl_net_dialer_http_request_headers_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd) +{ + Eina_Iterator_Curl_Slist_Header *it; + + it = calloc(1, sizeof(Eina_Iterator_Curl_Slist_Header)); + EINA_SAFETY_ON_NULL_RETURN_VAL(it, NULL); + + it->head = pd->request.headers; + it->current = it->head; + + EINA_MAGIC_SET(&it->iterator, EINA_MAGIC_ITERATOR); + it->iterator.version = EINA_ITERATOR_VERSION; + it->iterator.next = FUNC_ITERATOR_NEXT(eina_iterator_curl_slist_header_next); + it->iterator.get_container = FUNC_ITERATOR_GET_CONTAINER(eina_iterator_curl_slist_header_get_container); + it->iterator.free = FUNC_ITERATOR_FREE(eina_iterator_curl_slist_header_free); + + return &it->iterator; +} + +EOLIAN static void +_efl_net_dialer_http_request_content_length_set(Eo *o, Efl_Net_Dialer_Http_Data *pd, int64_t length) +{ + Efl_Net_Dialer_Http_Primary_Mode pm; + CURLcode r; + + if (strcmp(pd->method, "POST") == 0) + r = curl_easy_setopt(pd->easy, CURLOPT_POSTFIELDSIZE_LARGE, length); + else + r = curl_easy_setopt(pd->easy, CURLOPT_INFILESIZE_LARGE, length); + if (r != CURLE_OK) + ERR("dialer=%p could not set file size %" PRId64 ": %s", + o, length, curl_easy_strerror(r)); + + pd->request.content_length = length; + if (length < 0) + return; + + pm = _efl_net_dialer_http_primary_mode_effective_get(pd); + if (pm == EFL_NET_DIALER_HTTP_PRIMARY_MODE_UPLOAD) + efl_event_callback_call(o, EFL_IO_SIZER_EVENT_SIZE_CHANGED, NULL); +} + +EOLIAN static int64_t +_efl_net_dialer_http_request_content_length_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd) +{ + return pd->request.content_length; +} + +EOLIAN static void +_efl_net_dialer_http_response_content_length_set(Eo *o, Efl_Net_Dialer_Http_Data *pd, int64_t length) +{ + Efl_Net_Dialer_Http_Primary_Mode pm; + + pd->response.content_length = length; + if (length < 0) + return; + + pm = _efl_net_dialer_http_primary_mode_effective_get(pd); + if (pm == EFL_NET_DIALER_HTTP_PRIMARY_MODE_DOWNLOAD) + efl_event_callback_call(o, EFL_IO_SIZER_EVENT_SIZE_CHANGED, NULL); +} + +EOLIAN static int64_t +_efl_net_dialer_http_response_content_length_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd) +{ + return pd->response.content_length; +} + +EOLIAN static void +_efl_net_dialer_http_response_content_type_set(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd, const char *content_type) +{ + eina_stringshare_replace(&pd->response.content_type, content_type); +} + +EOLIAN static const char * +_efl_net_dialer_http_response_content_type_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd) +{ + return pd->response.content_type; +} + +EOLIAN static Eina_Iterator * +_efl_net_dialer_http_response_headers_all_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd) +{ + return eina_list_iterator_new(pd->response.headers); +} + +EOLIAN static Eina_Iterator * +_efl_net_dialer_http_response_headers_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd) +{ + const Eina_List *lst = pd->response.last_request_header; + + if (lst) lst = lst->next; + else lst = pd->response.headers; + + return eina_list_iterator_new(lst); +} + +EOLIAN static void +_efl_net_dialer_http_response_headers_clear(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd) +{ + void *mem; + + EINA_LIST_FREE(pd->response.headers, mem) + free(mem); /* key and value are in the same memory */ + + pd->response.last_request_header = NULL; +} + +EOLIAN static void +_efl_net_dialer_http_progress_download_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd, uint64_t *now, uint64_t *total) +{ + if (now) *now = pd->progress.download.now; + if (total) *total = pd->progress.download.total; +} + +EOLIAN static void +_efl_net_dialer_http_progress_upload_get(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd, uint64_t *now, uint64_t *total) +{ + if (now) *now = pd->progress.upload.now; + if (total) *total = pd->progress.upload.total; +} + +#include "efl_net_dialer_http.eo.c" diff --git a/src/lib/ecore_con/efl_net_dialer_http.eo b/src/lib/ecore_con/efl_net_dialer_http.eo new file mode 100644 index 0000000000..84e6a3f009 --- /dev/null +++ b/src/lib/ecore_con/efl_net_dialer_http.eo @@ -0,0 +1,314 @@ +import efl_net_http_types; + +enum Efl.Net.Dialer.Http.Primary_Mode { + auto, + download, + upload, +} + +class Efl.Net.Dialer.Http (Efl.Loop_User, Efl.Net.Dialer, Efl.Io.Reader, Efl.Io.Writer, Efl.Io.Sizer) { /* TODO: reader/writer should be from dialer->socket, but are being missed somehow... */ + [[HTTP Dialer (Client). + + The effective URL in use, if @.allow_redirects is $true will be + available as @Efl.Net.Socket.address_remote. The + @Efl.Net.Socket.address_local is an IP:PORT pair. + + The dialer can do bi-directional information exchange. It can + also do a PUT and upload a file, or GET and download one. Anoter + case is to do a POST with some form values, including a file, + and retrieve its headers and response body. To make usage more + streamlined, choose your primary interest with @.primary_mode + then get some properties such as @Efl.Io.Sizer.size to report or + use what matters to your use case. + + If @.allow_redirects is $true, multiple "connected" and + "resolved" signals will be dispatched, one for each + connection. Then @.response_status and @.response_headers_get + will keep changing. Using @.response_headers_all_get one can see + the whole history of headers and connections. + + To enable debugging use EINA_LOG_LEVELS=ecore_con:4 environment + variable. + + @since 1.19 + ]] + methods { + @property method { + [[The HTTP method to use. + + A string representing the HTTP method to use, such as + GET, POST, HEAD, PUT, DELETE... + + This should be set before dialing. + ]] + get { } + set { } + values { + method: string; + } + } + + @property primary_mode { + [[Is this request primarily a download or upload? + + This property will change the behavior of @Efl.Io.Sizer: + + - if @Efl.Net.Dialer.Http.Primary_Mode.auto, then + @Efl.Net.Dialer.Http.Primary_Mode.download or + @Efl.Net.Dialer.Http.Primary_Mode.upload will be + choosen based on the @.method: if "PUT", then it's + upload, otherwise it's download. + + - if @Efl.Net.Dialer.Http.Primary_Mode.upload, applying + a new size with @Efl.Io.Sizer.resize or + @Efl.Io.Sizer.size.set will specify the + "Content-Length" to upload. If no size is previously + set, then the upload will happen in + "Transfer-encoding: chunked". + + - if @Efl.Net.Dialer.Http.Primary_Mode.download, then + @Efl.Io.Sizer.size.get will report the + "Content-Length" provided by the server, if any. + + If is worth to mention that one can provide and + retrieve these values using @.request_headers_get (to + send) and @.response_headers_get (what was received), + as well as using the specific properties + @.request_content_length (upload) and + @.response_content_length (download). + ]] + get { + [[The effective primary mode. + + This will return one of + @Efl.Net.Dialer.Http.Primary_Mode.download or + @Efl.Net.Dialer.Http.Primary_Mode.upload. If "auto" + was set (the default), then it will pick the best + based on the @.method in use. + ]] + } + set { } + values { + primary_mode: Efl.Net.Dialer.Http.Primary_Mode; + } + } + + @property user_agent { + [[The User-Agent to specify. + + This should be set before dialing. + ]] + get { } + set { } + values { + user_agent: string; + } + } + + @property http_version { + [[The HTTP version to use. + + This should be set before dialing. + + Once connected, it will change to the actual connection + HTTP version, so check after "connected" event. + ]] + get { } + set { } + values { + http_version: Efl.Net.Http.Version; + } + } + + @property authentication { + [[HTTP authentication to use. + + This should be set before dialing. + ]] + get { } + set { } + values { + username: string; + password: string; + method: Efl.Net.Http.Authentication_Method @optional; [[authentication method to use, defaults to @Efl.Net.Http.Authentication_Method.basic]] + restricted: bool @optional; [[restrict method]] + } + } + + @property allow_redirects { + [[Allow HTTP redirects to be followed. + + This should be set before dialing. + ]] + get { } + set { } + values { + allow_redirects: bool; + } + } + + request_header_add { + [[Add a request header 'key: value'. + + See @.request_headers_clear + + This should be called before dialing. + ]] + params { + @in key: string; + @in value: string; + } + } + + request_headers_clear { + [[Clear all request headers. + + See @.request_header_add + + This should be called before dialing. + ]] + } + + request_headers_get { + [[Return an iterator to the key-value pairs for request headers]] + return: free(own(iterator), eina_iterator_free) @warn_unused; + } + + @property request_content_length { + [["Content-Length:" Header used for uploading/sending. + + To unset use -1 + ]] + get { } + set { } + values { + length: int64; + } + } + + @property response_content_length { + [["Content-Length:" Header used for downloading/receiving. + + If unset is -1. + ]] + get { } + set @protected { } + values { + length: int64; + } + } + + @property response_content_type { + [["Content-Type:" Header used for downloading/receiving]] + get { } + set @protected { } + values { + content_type: string; + } + } + + @property response_status { + [[The HTTP response status of this request. + + It will be 0 if not connected, otherwise will be what is + returned by the server, such as. + + See https://en.wikipedia.org/wiki/List_of_HTTP_status_codes + + This will be usable after "connected" event is dispatched. + ]] + get { } + values { + status_code: Efl.Net.Http.Status; + } + } + + response_headers_get { + [[Return an iterator to the key-value pairs for the last response headers. + + Since multiple requests can happen if @.allow_redirects + is true, then all headers will be accumulated. This + method returns only the headers for the last request. + + To know all the headers, check @.response_headers_all_get. + + This will be usable after "headers,done" event is dispatched. + ]] + return: free(own(iterator), eina_iterator_free) @warn_unused; + } + + response_headers_all_get { + [[Return an iterator to the key-value pairs for all response headers. + + Since multiple requests can happen if @.allow_redirects + is true, then all headers will be accumulated. To know + when new request is started, check for headers with keys + being NULL, the value will be the "HTTP/VERSION RESPONSE" + string received from the host, such as: + + - key=NULL, value="HTTP/1.1 302 Found" + - key="Location", value="http://someredirect.com" + - key=NULL, value="HTTP/1.1 200 Ok" + - key="Content-Type", value="text/html" + + Which mean the original request had a redirect to + http://someredirect.com. + + To receive an iterator to just the last request, use + @.response_headers_get + + This will be usable after "headers,done" event is dispatched. + ]] + return: free(own(iterator), eina_iterator_free) @warn_unused; + } + + response_headers_clear { + [[Save some memory by disposing the received headers]] + } + + @property progress_download { + [[How many bytes were downloaded and how much was expected.]] + get { } + values { + downloaded: uint64 @optional; + total: uint64 @optional; [[0 if unknown]] + } + } + + @property progress_upload { + [[How many bytes were uploaded and how much was expected.]] + get { } + values { + uploaded: uint64 @optional; + total: uint64 @optional; [[0 if unknown]] + } + } + } + + events { + headers,done; [[Notifies all headers were parsed and are available.]] + } + + implements { + Efl.Object.constructor; + Efl.Object.destructor; + Efl.Net.Dialer.dial; + Efl.Net.Dialer.address_dial; + Efl.Net.Dialer.connected; + Efl.Net.Dialer.timeout_dial; + Efl.Net.Socket.address_local; + Efl.Net.Socket.address_remote; + Efl.Io.Reader.read; + Efl.Io.Reader.can_read.get; + Efl.Io.Reader.can_read.set; + Efl.Io.Reader.eos.get; + Efl.Io.Reader.eos.set; + Efl.Io.Writer.write; + Efl.Io.Writer.can_write.get; + Efl.Io.Writer.can_write.set; + Efl.Io.Closer.close; + Efl.Io.Closer.closed.get; + Efl.Io.Sizer.resize; + Efl.Io.Sizer.size.get; + } +} diff --git a/src/lib/ecore_con/efl_net_http_types.eot b/src/lib/ecore_con/efl_net_http_types.eot new file mode 100644 index 0000000000..6fb6911dec --- /dev/null +++ b/src/lib/ecore_con/efl_net_http_types.eot @@ -0,0 +1,176 @@ +import eina_types; + +enum Efl.Net.Http.Version { + v1_0 = 100, [[1.0]] + v1_1 = 101, [[1.1]] + v2_0 = 200, [[2.0]] +} + +enum Efl.Net.Http.Authentication_Method { + none = 0, + basic = (1 << 0), + digest = (1 << 1), + negotiate = (1 << 2), + ntlm = (1 << 3), + ntlm_winbind = (1 << 4), + any_safe = Efl.Net.Http.Authentication_Method.digest | Efl.Net.Http.Authentication_Method.negotiate | Efl.Net.Http.Authentication_Method.ntlm | Efl.Net.Http.Authentication_Method.ntlm_winbind, + any = Efl.Net.Http.Authentication_Method.any_safe | Efl.Net.Http.Authentication_Method.basic, +} + +enum Efl.Net.Http.Status { + [[Common HTTP status codes]] + + unknown = 0, [[unknown status, likely not connected]] + + /* Informational: 1xx */ + continue = 100, + switching_protocols = 101, + checkpoint = 103, /* unofficial extension */ + processing = 102, + + /* success: 2xx */ + ok = 200, + created = 201, + accepted = 202, + non_authoritative_information = 203, + no_content = 204, + reset_content = 205, + partial_content = 206, + multi_status = 207, + already_reported = 208, + im_used = 226, + + /* redirection: 3xx */ + multiple_choices = 300, + moved_permanently = 301, + found = 302, + see_other = 303, + not_modified = 304, + use_proxy = 305, + switch_proxy = 306, + temporary_redirect = 307, + permanent_redirect = 308, + + /* client error: 4xx */ + bad_request = 400, + unauthorized = 401, + payment_required = 402, + forbidden = 403, + not_found = 404, + method_not_allowed = 405, + not_acceptable = 406, + proxy_authentication_required = 407, + request_timeout = 408, + conflict = 409, + gone = 410, + length_required = 411, + precondition_failed = 412, + payload_too_large = 413, + uri_too_long = 414, + unsupported_media_type = 415, + range_not_satisfiable = 416, + expectation_failed = 417, + misdirected_request = 421, + unprocessable_entity = 422, + locked = 423, + failed_dependency = 424, + upgrade_required = 426, + precondition_required = 428, + too_many_requests = 429, + request_header_fields_too_large = 431, + login_timeout = 440, /* unofficial extension */ + no_response = 444, /* unofficial extension */ + retry_with = 449, /* unofficial extension */ + blocked_by_windows_parental_controls = 450, /* unofficial extension */ + unavailable_for_legal_reasons = 451, + ssl_certificate_error = 495, /* unofficial extension */ + ssl_certificate_required = 496, /* unofficial extension */ + http_request_sent_to_https_port = 497, /* unofficial extension */ + request_has_been_forbidden_by_antivirus = 499, + + /* server error: 5xx */ + internal_server_error = 500, + not_implemented = 501, + bad_gateway = 502, + service_unavailable = 503, + gateway_timeout = 504, + http_version_not_supported = 505, + variant_also_negotiates = 506, + insufficient_storage = 507, + loop_detected = 508, + bandwidth_limit_exceeded = 509, /* unofficial extension */ + not_extended = 510, + network_authentication_required = 511, +} + +struct Efl.Net.Http.Header { + [[An HTTP Header. + + Do not assume strings are Eina_Stringshare and they may be + NULL. The key and value should not include any trailing + whitespace. + + There is a special case for response headers when + "allow_redirects" is enabled, in that case + efl_net_dialer_http_response_headers_all_get() will return some + items with key being NULL, that notifies of a new request as + described in the value "HTTP/1.1 200 Ok". + ]] + key: string; [[for response headers this may be null to indicate a new request response, then the value will be a line such as 'HTTP/1.1 200 Ok']] + value: string; +} + +var @extern Efl.Net.Http.Error.BAD_CONTENT_ENCODING: Eina.Error; +var @extern Efl.Net.Http.Error.BAD_DOWNLOAD_RESUME: Eina.Error; +var @extern Efl.Net.Http.Error.BAD_FUNCTION_ARGUMENT: Eina.Error; +var @extern Efl.Net.Http.Error.CHUNK_FAILED: Eina.Error; +var @extern Efl.Net.Http.Error.CONV_FAILED: Eina.Error; +var @extern Efl.Net.Http.Error.CONV_REQD: Eina.Error; +var @extern Efl.Net.Http.Error.COULDNT_CONNECT: Eina.Error; +var @extern Efl.Net.Http.Error.COULDNT_RESOLVE_HOST: Eina.Error; +var @extern Efl.Net.Http.Error.COULDNT_RESOLVE_PROXY: Eina.Error; +var @extern Efl.Net.Http.Error.FAILED_INIT: Eina.Error; +var @extern Efl.Net.Http.Error.FILE_COULDNT_READ_FILE: Eina.Error; +var @extern Efl.Net.Http.Error.FILESIZE_EXCEEDED: Eina.Error; +var @extern Efl.Net.Http.Error.FUNCTION_NOT_FOUND: Eina.Error; +var @extern Efl.Net.Http.Error.GOT_NOTHING: Eina.Error; +var @extern Efl.Net.Http.Error.HTTP2: Eina.Error; +var @extern Efl.Net.Http.Error.HTTP2_STREAM: Eina.Error; +var @extern Efl.Net.Http.Error.HTTP_POST_ERROR: Eina.Error; +var @extern Efl.Net.Http.Error.HTTP_RETURNED_ERROR: Eina.Error; +var @extern Efl.Net.Http.Error.INTERFACE_FAILED: Eina.Error; +var @extern Efl.Net.Http.Error.LOGIN_DENIED: Eina.Error; +var @extern Efl.Net.Http.Error.NO_CONNECTION_AVAILABLE: Eina.Error; +var @extern Efl.Net.Http.Error.NOT_BUILT_IN: Eina.Error; +var @extern Efl.Net.Http.Error.OPERATION_TIMEDOUT: Eina.Error; +var @extern Efl.Net.Http.Error.PARTIAL_FILE: Eina.Error; +var @extern Efl.Net.Http.Error.PEER_FAILED_VERIFICATION: Eina.Error; +var @extern Efl.Net.Http.Error.RANGE_ERROR: Eina.Error; +var @extern Efl.Net.Http.Error.READ_ERROR: Eina.Error; +var @extern Efl.Net.Http.Error.RECV_ERROR: Eina.Error; +var @extern Efl.Net.Http.Error.REMOTE_ACCESS_DENIED: Eina.Error; +var @extern Efl.Net.Http.Error.REMOTE_DISK_FULL: Eina.Error; +var @extern Efl.Net.Http.Error.REMOTE_FILE_EXISTS: Eina.Error; +var @extern Efl.Net.Http.Error.REMOTE_FILE_NOT_FOUND: Eina.Error; +var @extern Efl.Net.Http.Error.SEND_ERROR: Eina.Error; +var @extern Efl.Net.Http.Error.SEND_FAIL_REWIND: Eina.Error; +var @extern Efl.Net.Http.Error.SSL_CACERT: Eina.Error; +var @extern Efl.Net.Http.Error.SSL_CACERT_BADFILE: Eina.Error; +var @extern Efl.Net.Http.Error.SSL_CERTPROBLEM: Eina.Error; +var @extern Efl.Net.Http.Error.SSL_CIPHER: Eina.Error; +var @extern Efl.Net.Http.Error.SSL_CONNECT_ERROR: Eina.Error; +var @extern Efl.Net.Http.Error.SSL_CRL_BADFILE: Eina.Error; +var @extern Efl.Net.Http.Error.SSL_ENGINE_INITFAILED: Eina.Error; +var @extern Efl.Net.Http.Error.SSL_ENGINE_NOTFOUND: Eina.Error; +var @extern Efl.Net.Http.Error.SSL_ENGINE_SETFAILED: Eina.Error; +var @extern Efl.Net.Http.Error.SSL_INVALIDCERTSTATUS: Eina.Error; +var @extern Efl.Net.Http.Error.SSL_ISSUER_ERROR: Eina.Error; +var @extern Efl.Net.Http.Error.SSL_PINNEDPUBKEYNOTMATCH: Eina.Error; +var @extern Efl.Net.Http.Error.SSL_SHUTDOWN_FAILED: Eina.Error; +var @extern Efl.Net.Http.Error.TOO_MANY_REDIRECTS: Eina.Error; +var @extern Efl.Net.Http.Error.UNKNOWN_OPTION: Eina.Error; +var @extern Efl.Net.Http.Error.UNSUPPORTED_PROTOCOL: Eina.Error; +var @extern Efl.Net.Http.Error.UPLOAD_FAILED: Eina.Error; +var @extern Efl.Net.Http.Error.URL_MALFORMAT: Eina.Error; +var @extern Efl.Net.Http.Error.USE_SSL_FAILED: Eina.Error; +var @extern Efl.Net.Http.Error.WRITE_ERROR: Eina.Error;