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;