efl/src/lib/ecore_con/efl_net_dialer_http.c

2422 lines
76 KiB
C

#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 <config.h>
#endif
#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 <ctype.h>
#include <fcntl.h>
/* 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__)
#define curl_getdate(s, unused) _c->curl_getdate(s, unused)
#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;
Eo *fdhandler;
Eina_Stringshare *address_dial;
Eina_Stringshare *proxy;
Eina_Stringshare *cookie_jar;
Eina_Stringshare *address_local;
Eina_Stringshare *address_remote;
Eina_Stringshare *method;
Eina_Stringshare *user_agent;
Ecore_Thread *libproxy_thread;
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;
struct {
Eina_Stringshare *ca;
Eina_Stringshare *crl;
Eina_Bool verify_peer;
Eina_Bool verify_hostname;
} ssl;
Eina_Future *pending_close;
unsigned int in_curl_callback;
SOCKET fd;
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 close_on_exec;
Eina_Bool close_on_invalidate;
Eina_Bool pending_eos;
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_finished_object_deleted(void *data, const Efl_Event *event)
{
Eina_List **p_finished = data;
*p_finished = eina_list_remove(*p_finished, event->object);
}
static void
_efl_net_dialer_http_curlm_check_finished_object_add(Eina_List **p_finished, Eo *dialer)
{
if (eina_list_data_find(*p_finished, dialer)) return;
efl_event_callback_add(dialer, EFL_EVENT_DEL,
_efl_net_dialer_http_curlm_check_finished_object_deleted,
p_finished);
*p_finished = eina_list_append(*p_finished, dialer);
}
static void
_efl_net_dialer_http_curlm_check_finished_object_remove(Eina_List **p_finished, Eo *dialer)
{
efl_event_callback_del(dialer, EFL_EVENT_DEL,
_efl_net_dialer_http_curlm_check_finished_object_deleted,
p_finished);
*p_finished = eina_list_remove(*p_finished, dialer);
}
static void
_efl_net_dialer_http_curlm_check(Efl_Net_Dialer_Http_Curlm *cm)
{
CURLMsg *msg;
int remaining;
Eina_List *finished = NULL;
Eo *dialer;
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) && (priv))
{
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_net_dialer_http_curlm_check_finished_object_add(&finished, dialer);
}
if (msg->msg != CURLMSG_DONE) continue;
if (!efl_io_closer_closed_get(dialer))
{
DBG("HTTP dialer=%p eos", dialer);
_efl_net_dialer_http_curlm_check_finished_object_add(&finished, dialer);
}
else
DBG("HTTP dialer=%p already closed", dialer);
}
}
while (finished)
{
Efl_Net_Dialer_Http_Data *pd;
dialer = finished->data;
efl_ref(dialer);
pd = efl_data_scope_get(dialer, MY_CLASS);
if (pd->error)
efl_event_callback_call(dialer, EFL_NET_DIALER_EVENT_ERROR, &pd->error);
if (pd->recv.used > 0) pd->pending_eos = EINA_TRUE;
else
{
if (!efl_io_closer_closed_get(dialer))
{
efl_io_reader_eos_set(dialer, EINA_TRUE);
if (!efl_io_closer_closed_get(dialer))
efl_io_closer_close(dialer);
}
}
_efl_net_dialer_http_curlm_check_finished_object_remove(&finished, dialer);
efl_unref(dialer);
}
}
static void
_efl_net_dialer_http_curlm_timer_do(void *data, const Efl_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_added, seconds),
efl_event_callback_add(efl_added, EFL_LOOP_TIMER_EVENT_TICK, _efl_net_dialer_http_curlm_timer_do, cm));
EINA_SAFETY_ON_NULL_RETURN_VAL(cm->timer, -1);
}
return 0;
}
static void
_efl_net_dialer_http_curlm_event_fd_read(void *data, const Efl_Event *event)
{
Efl_Net_Dialer_Http_Curlm *cm = data;
SOCKET fd = efl_loop_fd_get(event->object);
CURLMcode r;
r = curl_multi_socket_action(cm->multi, fd, CURL_CSELECT_IN, &cm->running);
if (r != CURLM_OK)
ERR("socket action CURL_CSELECT_IN fd=" SOCKET_FMT " 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 Efl_Event *event)
{
Efl_Net_Dialer_Http_Curlm *cm = data;
SOCKET fd = efl_loop_fd_get(event->object);
CURLMcode r;
r = curl_multi_socket_action(cm->multi, fd, CURL_CSELECT_OUT, &cm->running);
if (r != CURLM_OK)
ERR("socket action CURL_CSELECT_OUT fd=" SOCKET_FMT " 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);
if (!priv) return -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,
efl_loop_fd_set(efl_added, fd));
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);
if (was_read && !is_read)
{
efl_event_callback_del(fdhandler, EFL_LOOP_FD_EVENT_READ, _efl_net_dialer_http_curlm_event_fd_read, cm);
}
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);
}
if (was_write && !is_write)
{
efl_event_callback_del(fdhandler, EFL_LOOP_FD_EVENT_WRITE, _efl_net_dialer_http_curlm_event_fd_write, cm);
}
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);
}
efl_key_data_set(fdhandler, "curl_flags", (void *)(intptr_t)what);
}
return 0;
}
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);
DBG("removed handle cm=%p multi=%p easy=%p: %s",
cm, cm->multi, handle, curl_multi_strerror(r));
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)
{
DBG("cleaned up cm=%p multi=%p", cm, cm->multi);
curl_multi_cleanup(cm->multi);
cm->multi = NULL;
if (cm->timer)
{
efl_del(cm->timer);
cm->timer = 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_safe(Eo *o, Efl_Net_Dialer_Http_Data *pd, Eina_Rw_Slice rw_slice)
{
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;
if (rw_slice.len == 0)
{
pd->pause |= CURLPAUSE_SEND;
return CURL_READFUNC_PAUSE;
}
return rw_slice.len;
}
static CURL *
_efl_net_dialer_http_curl_safe_begin(Eo *o, Efl_Net_Dialer_Http_Data *pd)
{
if (!o || !pd)
return NULL;
if (efl_io_closer_closed_get(o))
return NULL;
pd->in_curl_callback++;
efl_ref(o);
return pd->easy;
}
static Eina_Value
_efl_net_dialer_http_curl_cleanup(void *data, const Eina_Value v)
{
CURL *easy = data;
DBG("cleanup curl=%p", easy);
curl_easy_cleanup(easy);
return v;
}
static Eina_Value
_efl_net_dialer_http_curl_cleanup_error(void *data, const Eina_Error err)
{
CURL *easy = data;
DBG("cleanup curl=%p, promise error=%d '%s'", easy, err, eina_error_msg_get(err));
curl_easy_cleanup(easy);
return EINA_VALUE_EMPTY;
}
static void
_efl_net_dialer_http_curl_safe_end(Eo *o, Efl_Net_Dialer_Http_Data *pd, CURL *easy)
{
Eina_Future *f;
int refs;
refs = efl_ref_count(o);
if (refs >= 2)
{
pd->in_curl_callback--;
efl_unref(o);
return;
}
if (pd->pending_close)
eina_future_cancel(pd->pending_close);
if (!pd->easy)
{
efl_unref(o);
return;
}
pd->easy = NULL;
efl_unref(o);
curl_easy_setopt(easy, CURLOPT_PRIVATE, NULL);
curl_easy_setopt(easy, CURLOPT_DEBUGFUNCTION, NULL);
curl_easy_setopt(easy, CURLOPT_DEBUGDATA, NULL);
curl_easy_setopt(easy, CURLOPT_XFERINFOFUNCTION, NULL);
curl_easy_setopt(easy, CURLOPT_XFERINFODATA, NULL);
curl_easy_setopt(easy, CURLOPT_HEADERFUNCTION, NULL);
curl_easy_setopt(easy, CURLOPT_HEADERDATA, NULL);
curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, NULL);
curl_easy_setopt(easy, CURLOPT_WRITEDATA, NULL);
curl_easy_setopt(easy, CURLOPT_READFUNCTION, NULL);
curl_easy_setopt(easy, CURLOPT_READDATA, NULL);
curl_easy_setopt(easy, CURLOPT_OPENSOCKETFUNCTION, NULL);
curl_easy_setopt(easy, CURLOPT_OPENSOCKETDATA, NULL);
curl_easy_setopt(easy, CURLOPT_NOPROGRESS, 1L);
curl_easy_setopt(easy, CURLOPT_VERBOSE, 0L);
/* object deleted from CURL callback, CURL* easy was
* dissociated and we must delete it ourselves.
*/
f = efl_loop_job(efl_loop_get(o));
eina_future_then_from_desc(f, eina_future_cb_easy(.success = _efl_net_dialer_http_curl_cleanup,
.error = _efl_net_dialer_http_curl_cleanup_error,
.data = easy));
DBG("dialer=%p deleted from CURL callback, cleanup curl from job=%p.", o, f);
}
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};
CURL *easy;
size_t ret;
easy = _efl_net_dialer_http_curl_safe_begin(o, pd);
if (!easy) return CURL_READFUNC_PAUSE;
ret = _efl_net_dialer_http_send_data_safe(o, pd, rw_slice);
_efl_net_dialer_http_curl_safe_end(o, pd, easy);
DBG("dialer=%p in=%zd used=%zd", o, rw_slice.len, ret);
return ret;
}
/* take data from curl into our internal buffer until
* efl_io_reader_read() consumes it
*/
static size_t
_efl_net_dialer_http_receive_data_safe(Eo *o, Efl_Net_Dialer_Http_Data *pd, Eina_Slice ro_slice)
{
Eina_Rw_Slice rw_slice = {
.bytes = pd->recv.bytes + pd->recv.used,
.len = pd->recv.limit - pd->recv.used,
};
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 < ro_slice.len)
{
/* throttle CURL and let users read */
DBG("dialer=%p in=%zd, available %zd (limit=%zd)",
o, ro_slice.len, pd->recv.limit - pd->recv.used, pd->recv.limit);
efl_io_reader_can_read_set(o, EINA_TRUE);
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);
return rw_slice.len;
}
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_Slice ro_slice = {
.bytes = buffer,
.len = count * nitems,
};
CURL *easy;
size_t ret;
easy = _efl_net_dialer_http_curl_safe_begin(o, pd);
if (!easy) return CURL_WRITEFUNC_PAUSE;
ret = _efl_net_dialer_http_receive_data_safe(o, pd, ro_slice);
_efl_net_dialer_http_curl_safe_end(o, pd, easy);
if (ret == CURL_WRITEFUNC_PAUSE)
DBG("dialer=%p in=%zd is now paused", o, ro_slice.len);
else
DBG("dialer=%p in=%zd used=%zd", o, ro_slice.len, ret);
return ret;
}
static size_t
_efl_net_dialer_http_receive_header_safe(Eo *o, Efl_Net_Dialer_Http_Data *pd, Eina_Slice ro_slice)
{
Efl_Net_Http_Header *h;
char *p;
if (ro_slice.len == 0)
{
if (!pd->connected) _efl_net_dialer_http_connected(o, pd);
return 0;
}
h = malloc(sizeof(Efl_Net_Http_Header) + ro_slice.len + 1);
EINA_SAFETY_ON_NULL_RETURN_VAL(h, 0);
h->key = p = (char *)h + sizeof(Efl_Net_Http_Header);
memcpy(p, ro_slice.mem, ro_slice.len);
p[ro_slice.len] = '\0';
h->value = p = strchr(p, ':');
if (!p)
p = (char *)h->key + ro_slice.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 + ro_slice.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]))
{
if (!pd->connected) _efl_net_dialer_http_connected(o, pd);
if (pd->pending_headers_done) _efl_net_dialer_http_headers_done(o, pd);
free(h);
return ro_slice.len;
}
if (!h->value)
{
if (strncmp(h->key, "HTTP/", strlen("HTTP/")) != 0)
DBG("unexpected header '" EINA_SLICE_STR_FMT "'", EINA_SLICE_STR_PRINT(ro_slice));
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 ro_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);
Eina_Slice ro_slice = {
.mem = buffer,
.len = count * nitems,
};
CURL *easy;
size_t ret;
easy = _efl_net_dialer_http_curl_safe_begin(o, pd);
if (!easy) return 0;
ret = _efl_net_dialer_http_receive_header_safe(o, pd, ro_slice);
_efl_net_dialer_http_curl_safe_end(o, pd, easy);
DBG("dialer=%p in=%zd used=%zd", o, ro_slice.len, ret);
return ret;
}
static curl_socket_t
_efl_net_dialer_http_socket_open(void *data, curlsocktype purpose EINA_UNUSED, struct curl_sockaddr *addr)
{
Eo *o = data;
Efl_Net_Dialer_Http_Data *pd = efl_data_scope_get(o, MY_CLASS);
pd->fd = efl_net_socket4(addr->family, addr->socktype, addr->protocol, pd->close_on_exec);
if (pd->fd == INVALID_SOCKET)
ERR("could not create curl socket family=%d, type=%d, protocol=%d",
addr->family, addr->socktype, addr->protocol);
else
DBG("socket(%d, %d, %d) = " SOCKET_FMT,
addr->family, addr->socktype, addr->protocol, pd->fd);
return pd->fd;
}
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_BUFFERSIZE, (long)pd->recv.limit);
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_OPENSOCKETFUNCTION, _efl_net_dialer_http_socket_open);
curl_easy_setopt(pd->easy, CURLOPT_OPENSOCKETDATA, o);
curl_easy_setopt(pd->easy, CURLOPT_NOPROGRESS, 0L);
curl_easy_setopt(pd->easy, CURLOPT_NOSIGNAL, 1L);
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_http_ssl_verify_set(o, EINA_TRUE, EINA_TRUE);
efl_net_dialer_timeout_dial_set(o, 30.0);
return o;
}
EOLIAN static void
_efl_net_dialer_http_efl_object_invalidate(Eo *o, Efl_Net_Dialer_Http_Data *pd)
{
if (pd->libproxy_thread)
{
ecore_thread_cancel(pd->libproxy_thread);
pd->libproxy_thread = NULL;
}
if (pd->in_curl_callback)
{
EINA_SAFETY_ON_TRUE_RETURN(pd->easy != NULL);
EINA_SAFETY_ON_TRUE_RETURN(pd->pending_close != NULL);
}
else if (pd->pending_close)
{
eina_future_cancel(pd->pending_close);
efl_event_freeze(o);
efl_io_closer_close(o);
efl_event_thaw(o);
}
else if (efl_io_closer_close_on_invalidate_get(o) &&
(!efl_io_closer_closed_get(o)))
{
efl_event_freeze(o);
efl_io_closer_close(o);
efl_event_thaw(o);
}
efl_invalidate(efl_super(o, MY_CLASS));
}
EOLIAN static void
_efl_net_dialer_http_efl_object_destructor(Eo *o, Efl_Net_Dialer_Http_Data *pd)
{
efl_net_dialer_http_response_headers_clear(o);
if (pd->easy)
{
DBG("cleanup curl=%p", 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->proxy, NULL);
eina_stringshare_replace(&pd->cookie_jar, 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);
eina_stringshare_replace(&pd->ssl.ca, NULL);
eina_stringshare_replace(&pd->ssl.crl, NULL);
}
static void
_efl_net_dialer_http_curl_start(Eo *o, Efl_Net_Dialer_Http_Data *pd)
{
Efl_Net_Dialer_Http_Curlm *cm;
// TODO: move this to be per-loop once multiple mainloops are supported
// this would need to attach something to the loop
cm = &_cm_global;
cm->loop = efl_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;
}
pd->cm = cm;
DBG("started curl request easy=%p, cm=%p", pd->easy, pd->cm);
}
typedef struct _Efl_Net_Dialer_Http_Libproxy_Context {
Eo *o;
char *url;
char *proxy;
} Efl_Net_Dialer_Http_Libproxy_Context;
static void
_efl_net_dialer_http_libproxy_run(void *data, Ecore_Thread *thread)
{
Efl_Net_Dialer_Http_Libproxy_Context *ctx = data;
char **proxies = ecore_con_libproxy_proxies_get(ctx->url, thread);
char **itr;
if (!proxies) return;
for (itr = proxies; *itr != NULL; itr++)
{
if (itr != proxies) free(*itr);
else
{
if (strcmp(*itr, "direct://") != 0) ctx->proxy = *itr;
else
{
/* NULL not "" so we let curl use envvars */
ctx->proxy = NULL;
free(*itr);
}
}
}
free(proxies);
}
static void
_efl_net_dialer_http_libproxy_context_free(Efl_Net_Dialer_Http_Libproxy_Context *ctx)
{
free(ctx->proxy);
free(ctx->url);
free(ctx);
}
static void
_efl_net_dialer_http_libproxy_end(void *data, Ecore_Thread *thread)
{
Efl_Net_Dialer_Http_Libproxy_Context *ctx = data;
Eo *o = ctx->o;
Efl_Net_Dialer_Http_Data *pd = efl_data_scope_get(o, MY_CLASS);
EINA_SAFETY_ON_NULL_RETURN(pd);
if (pd->libproxy_thread == thread)
pd->libproxy_thread = NULL;
if (ctx->proxy)
{
CURLcode r = curl_easy_setopt(pd->easy, CURLOPT_PROXY, ctx->proxy);
if (r != CURLE_OK)
ERR("dialer=%p could not set proxy to '%s': %s",
o, ctx->proxy, curl_easy_strerror(r));
else
DBG("libproxy said %s for %s", ctx->proxy, pd->address_dial);
}
_efl_net_dialer_http_libproxy_context_free(ctx);
_efl_net_dialer_http_curl_start(o, pd);
}
static void
_efl_net_dialer_http_libproxy_cancel(void *data, Ecore_Thread *thread EINA_UNUSED)
{
Efl_Net_Dialer_Http_Libproxy_Context *ctx = data;
_efl_net_dialer_http_libproxy_context_free(ctx);
}
EOLIAN static Eina_Error
_efl_net_dialer_http_efl_net_dialer_dial(Eo *o, Efl_Net_Dialer_Http_Data *pd, const char *address)
{
struct curl_slist *sl_it;
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);
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;
}
for (sl_it = pd->request.headers; sl_it != NULL; sl_it = sl_it->next)
{
#define IS_HEADER(x) (strncasecmp(sl_it->data, x ":", strlen(x ":")) == 0)
if (IS_HEADER("Accept-Encoding"))
{
const char *value = strchr(sl_it->data, ':') + 1;
while (*value && isspace(*value))
value++;
r = curl_easy_setopt(pd->easy, CURLOPT_ENCODING, value);
if (r != CURLE_OK)
{
ERR("dialer=%p could not set HTTP Accept-Encoding '%s': %s",
o, value, curl_easy_strerror(r));
return EINVAL;
}
DBG("Using Accept-Encoding: %s", value);
}
else if (IS_HEADER("If-Modified-Since"))
{
const char *value = strchr(sl_it->data, ':') + 1;
long t;
r = curl_easy_setopt(pd->easy, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE);
if (r != CURLE_OK)
{
ERR("dialer=%p could not set HTTP If-Modified-Since condition: %s",
o, curl_easy_strerror(r));
return EINVAL;
}
while (*value && isspace(*value))
value++;
t = curl_getdate(value, NULL);
r = curl_easy_setopt(pd->easy, CURLOPT_TIMEVALUE, t);
if (r != CURLE_OK)
{
ERR("dialer=%p could not set HTTP If-Modified-Since value=%ld: %s",
o, t, curl_easy_strerror(r));
return EINVAL;
}
DBG("Using If-Modified-Since: %s [t=%ld]", value, t);
}
else if (IS_HEADER("If-Unmodified-Since"))
{
const char *value = strchr(sl_it->data, ':') + 1;
long t;
r = curl_easy_setopt(pd->easy, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFUNMODSINCE);
if (r != CURLE_OK)
{
ERR("dialer=%p could not set HTTP If-Unmodified-Since condition: %s",
o, curl_easy_strerror(r));
return EINVAL;
}
while (*value && isspace(*value))
value++;
t = curl_getdate(value, NULL);
r = curl_easy_setopt(pd->easy, CURLOPT_TIMEVALUE, t);
if (r != CURLE_OK)
{
ERR("dialer=%p could not set HTTP If-Unmodified-Since value=%ld: %s",
o, t, curl_easy_strerror(r));
return EINVAL;
}
DBG("Using If-Unmodified-Since: %s [t=%ld]", value, t);
}
#undef IS_HEADER
}
if (!pd->proxy)
{
Efl_Net_Dialer_Http_Libproxy_Context *ctx;
if (pd->libproxy_thread)
{
ecore_thread_cancel(pd->libproxy_thread);
pd->libproxy_thread = NULL;
}
ctx = calloc(1, sizeof(Efl_Net_Dialer_Http_Libproxy_Context));
EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, ENOMEM);
if (strstr(address, "://")) ctx->url = strdup(address);
else
{
ctx->url = malloc(strlen("http://") + strlen(address) + 1);
if (ctx->url)
{
memcpy(ctx->url, "http://", strlen("http://"));
memcpy(ctx->url + strlen("http://"), address, strlen(address) + 1);
}
}
EINA_SAFETY_ON_NULL_GOTO(ctx->url, url_error);
ctx->o = o;
pd->libproxy_thread = ecore_thread_feedback_run(_efl_net_dialer_http_libproxy_run,
NULL,
_efl_net_dialer_http_libproxy_end,
_efl_net_dialer_http_libproxy_cancel,
ctx, EINA_TRUE);
return 0;
url_error:
free(ctx);
return ENOMEM;
}
_efl_net_dialer_http_curl_start(o, pd);
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(const 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(const Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd)
{
return pd->connected;
}
EOLIAN static void
_efl_net_dialer_http_efl_net_dialer_proxy_set(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd, const char *proxy_url)
{
CURLcode r;
r = curl_easy_setopt(pd->easy, CURLOPT_PROXY, proxy_url);
if (r != CURLE_OK)
ERR("dialer=%p could not set proxy to '%s': %s",
o, proxy_url, curl_easy_strerror(r));
eina_stringshare_replace(&pd->proxy, proxy_url);
}
EOLIAN static const char *
_efl_net_dialer_http_efl_net_dialer_proxy_get(const Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd)
{
return pd->proxy;
}
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(const 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(const 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(const Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd)
{
return pd->address_remote;
}
static Eina_Error
_efl_net_dialer_http_pause_reset(Eo *o, Efl_Net_Dialer_Http_Data *pd)
{
CURLcode re;
CURLMcode rm;
Eina_Error err;
EINA_SAFETY_ON_TRUE_RETURN_VAL(pd->cm == NULL, EALREADY);
re = curl_easy_pause(pd->easy, pd->pause);
if (re != CURLE_OK)
{
err = _curlcode_to_eina_error(re);
ERR("dialer=%p could not unpause receive (flags=%#x): %s",
o, pd->pause, eina_error_msg_get(err));
return err;
}
rm = curl_multi_socket_action(pd->cm->multi, CURL_SOCKET_TIMEOUT, 0, &pd->cm->running);
if (rm != CURLM_OK)
{
err = _curlmcode_to_eina_error(rm);
ERR("dialer=%p could trigger timeout action: %s",
o, eina_error_msg_get(err));
return err;
}
_efl_net_dialer_http_curlm_check(pd->cm);
return 0;
}
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;
if (pd->pending_eos)
{
efl_io_reader_eos_set(o, EINA_TRUE);
efl_io_closer_close(o);
}
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->pending_eos) && (remaining == 0))
{
efl_io_reader_eos_set(o, EINA_TRUE);
efl_io_closer_close(o);
return 0;
}
if ((pd->pause & CURLPAUSE_RECV) && (pd->recv.used < pd->recv.limit))
{
Eina_Error err;
pd->pause &= ~CURLPAUSE_RECV;
err = _efl_net_dialer_http_pause_reset(o, pd);
if (err)
return err;
}
return 0;
}
EOLIAN static Eina_Bool
_efl_net_dialer_http_efl_io_reader_can_read_get(const 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(const 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 rm;
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;
err = _efl_net_dialer_http_pause_reset(o, pd);
if (err) goto error;
if (!pd->cm)
{
err = EISCONN;
goto error;
}
pd->error = 0;
rm = curl_multi_socket_action(pd->cm->multi,
pd->fd,
CURL_CSELECT_OUT, &pd->cm->running);
if (rm != CURLM_OK)
{
err = _curlcode_to_eina_error(rm);
ERR("dialer=%p could not trigger socket=" SOCKET_FMT " (fdhandler=%p) action: %s",
o, pd->fd, 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(const 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);
}
static Eina_Value _efl_net_dialer_http_pending_close(Eo *o, void *data, Eina_Value value);
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);
pd->fd = INVALID_SOCKET;
if (pd->in_curl_callback)
{
if ((!pd->pending_close) && (pd->easy))
{
efl_future_then(o, efl_loop_job(efl_loop_get(o)),
.success = _efl_net_dialer_http_pending_close,
.storage = &pd->pending_close);
DBG("dialer=%p closed from CURL callback, schedule close job=%p", o, pd->pending_close);
}
return 0;
}
if (!pd->easy) goto end;
if (pd->cm)
{
DBG("close dialer=%p, cm=%p, easy=%p", o, pd->cm, pd->easy);
_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);
efl_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_TRUE);
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;
}
static Eina_Value
_efl_net_dialer_http_pending_close(Eo *o, void *data EINA_UNUSED, const Eina_Value value EINA_UNUSED)
{
Efl_Net_Dialer_Http_Data *pd = efl_data_scope_get(o, MY_CLASS);
_efl_net_dialer_http_efl_io_closer_close(o, pd);
return value;
}
EOLIAN static Eina_Bool
_efl_net_dialer_http_efl_io_closer_closed_get(const Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd)
{
return pd->closed || (!!pd->pending_close);
}
EOLIAN static Eina_Bool
_efl_net_dialer_http_efl_io_closer_close_on_exec_set(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd, Eina_Bool close_on_exec)
{
#ifdef _WIN32
DBG("close on exec is not supported on windows");
pd->close_on_exec = close_on_exec;
return EINA_FALSE;
#else
Eina_Bool old = pd->close_on_exec;
pd->close_on_exec = close_on_exec;
if (pd->fd == INVALID_SOCKET) return EINA_TRUE; /* postpone until _efl_net_dialer_http_socket_open */
if (!eina_file_close_on_exec(pd->fd, close_on_exec))
{
ERR("fcntl(" SOCKET_FMT ", F_SETFD): %s", pd->fd, strerror(errno));
pd->close_on_exec = old;
return EINA_FALSE;
}
return EINA_TRUE;
#endif
}
EOLIAN static Eina_Bool
_efl_net_dialer_http_efl_io_closer_close_on_exec_get(const Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd)
{
return pd->close_on_exec;
}
EOLIAN static void
_efl_net_dialer_http_efl_io_closer_close_on_invalidate_set(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd, Eina_Bool close_on_invalidate)
{
pd->close_on_invalidate = close_on_invalidate;
}
EOLIAN static Eina_Bool
_efl_net_dialer_http_efl_io_closer_close_on_invalidate_get(const Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd)
{
return pd->close_on_invalidate;
}
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) ||
(strcmp(pd->method, "POST") == 0))
{
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(const 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;
}
static void
_efl_net_dialer_http_request_apply(Eo *o, Efl_Net_Dialer_Http_Data *pd, const char *method, Efl_Net_Dialer_Http_Primary_Mode primary_mode)
{
CURLcode r;
if (primary_mode == EFL_NET_DIALER_HTTP_PRIMARY_MODE_UPLOAD)
{
if (strcasecmp(method, "PUT") == 0)
r = curl_easy_setopt(pd->easy, CURLOPT_PUT, 1L);
else
{
/* upload with non PUT method:
* 1 - set CURLOPT_UPLOAD = 1, this forces httpreq = PUT
* 2 - set CURLOPT_CUSTOMREQUEST = method, this overrides
* the string to send.
*/
r = curl_easy_setopt(pd->easy, CURLOPT_UPLOAD, 1L);
if (r != CURLE_OK)
ERR("dialer=%p could not configure upload mode: %s",
o, curl_easy_strerror(r));
r = curl_easy_setopt(pd->easy, CURLOPT_CUSTOMREQUEST, method);
}
}
else
{
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, "HEAD") == 0)
r = curl_easy_setopt(pd->easy, CURLOPT_NOBODY, 1L);
else if (strcasecmp(method, "PUT") == 0)
{
if (primary_mode == EFL_NET_DIALER_HTTP_PRIMARY_MODE_AUTO)
r = curl_easy_setopt(pd->easy, CURLOPT_PUT, 1L);
else
{
r = curl_easy_setopt(pd->easy, CURLOPT_UPLOAD, 0L);
if (r != CURLE_OK)
ERR("dialer=%p could not configure no-upload mode: %s",
o, curl_easy_strerror(r));
r = curl_easy_setopt(pd->easy, CURLOPT_CUSTOMREQUEST, method);
}
}
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));
}
EOLIAN static void
_efl_net_dialer_http_method_set(Eo *o, Efl_Net_Dialer_Http_Data *pd, const char *method)
{
EINA_SAFETY_ON_NULL_RETURN(method);
_efl_net_dialer_http_request_apply(o, pd, method, pd->primary_mode);
eina_stringshare_replace(&pd->method, method);
}
EOLIAN static const char *
_efl_net_dialer_http_method_get(const 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, Efl_Net_Dialer_Http_Data *pd, Efl_Net_Dialer_Http_Primary_Mode primary_mode)
{
_efl_net_dialer_http_request_apply(o, pd, pd->method, primary_mode);
pd->primary_mode = primary_mode;
}
EOLIAN static Efl_Net_Dialer_Http_Primary_Mode
_efl_net_dialer_http_primary_mode_get(const 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(const 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(const 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(const 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(const 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(const 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(const 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(const 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(const 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(const 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(const 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;
}
EOLIAN static void
_efl_net_dialer_http_cookie_jar_set(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd, const char *path)
{
CURLcode r;
if (pd->cookie_jar)
{
r = curl_easy_setopt(pd->easy, CURLOPT_COOKIELIST, "FLUSH");
if (r != CURLE_OK)
ERR("dialer=%p could not flush cookie_jar to '%s': %s",
o, pd->cookie_jar, curl_easy_strerror(r));
r = curl_easy_setopt(pd->easy, CURLOPT_COOKIELIST, "ALL");
if (r != CURLE_OK)
ERR("dialer=%p could not empty cookie_jar: %s",
o, curl_easy_strerror(r));
r = curl_easy_setopt(pd->easy, CURLOPT_COOKIEFILE, NULL);
if (r != CURLE_OK)
ERR("dialer=%p could not unset cookie_jar (read): %s",
o, curl_easy_strerror(r));
r = curl_easy_setopt(pd->easy, CURLOPT_COOKIEJAR, NULL);
if (r != CURLE_OK)
ERR("dialer=%p could not unset cookie_jar (write): %s",
o, curl_easy_strerror(r));
}
if (!path) goto end;
r = curl_easy_setopt(pd->easy, CURLOPT_COOKIEFILE, path);
if (r != CURLE_OK)
ERR("dialer=%p could not set cookie_jar (read) to '%s': %s",
o, path, curl_easy_strerror(r));
r = curl_easy_setopt(pd->easy, CURLOPT_COOKIEJAR, path);
if (r != CURLE_OK)
ERR("dialer=%p could not set cookie_jar (write) to '%s': %s",
o, path, curl_easy_strerror(r));
end:
eina_stringshare_replace(&pd->cookie_jar, path);
}
EOLIAN static const char *
_efl_net_dialer_http_cookie_jar_get(const Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd)
{
return pd->cookie_jar;
}
EOLIAN static void
_efl_net_dialer_http_ssl_verify_set(Eo *o, Efl_Net_Dialer_Http_Data *pd, Eina_Bool peer, Eina_Bool hostname)
{
CURLcode r;
r = curl_easy_setopt(pd->easy, CURLOPT_SSL_VERIFYPEER, (long)peer);
if (r != CURLE_OK)
ERR("dialer=%p could not set SSL verify peer %d: %s",
o, peer, curl_easy_strerror(r));
/* VERIFYHOST used to take 1 as debug,
* it requires 2 to really enable it.
*/
r = curl_easy_setopt(pd->easy, CURLOPT_SSL_VERIFYHOST, (long)hostname * 2);
if (r != CURLE_OK)
ERR("dialer=%p could not set SSL verify hostname %d: %s",
o, hostname, curl_easy_strerror(r));
pd->ssl.verify_peer = peer;
pd->ssl.verify_hostname = hostname;
}
EOLIAN static void
_efl_net_dialer_http_ssl_verify_get(const Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd, Eina_Bool *peer, Eina_Bool *hostname)
{
if (peer) *peer = pd->ssl.verify_peer;
if (hostname) *hostname = pd->ssl.verify_hostname;
}
EOLIAN static void
_efl_net_dialer_http_ssl_certificate_authority_set(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd, const char *path)
{
CURLcode r;
struct stat st;
if (path && (stat(path, &st) == 0) && (S_ISDIR(st.st_mode)))
r = curl_easy_setopt(pd->easy, CURLOPT_CAPATH, path);
else
r = curl_easy_setopt(pd->easy, CURLOPT_CAINFO, path);
if (r != CURLE_OK)
ERR("dialer=%p could not set SSL CA '%s': %s",
o, path, curl_easy_strerror(r));
eina_stringshare_replace(&pd->ssl.ca, path);
}
EOLIAN static const char *
_efl_net_dialer_http_ssl_certificate_authority_get(const Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd)
{
return pd->ssl.ca;
}
EOLIAN static void
_efl_net_dialer_http_ssl_certificate_revocation_list_set(Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd, const char *path)
{
CURLcode r;
r = curl_easy_setopt(pd->easy, CURLOPT_CRLFILE, path);
if (r != CURLE_OK)
ERR("dialer=%p could not set SSL CRL '%s': %s",
o, path, curl_easy_strerror(r));
eina_stringshare_replace(&pd->ssl.crl, path);
}
EOLIAN static const char *
_efl_net_dialer_http_ssl_certificate_revocation_list_get(const Eo *o EINA_UNUSED, Efl_Net_Dialer_Http_Data *pd)
{
return pd->ssl.crl;
}
EOLIAN static int64_t
_efl_net_dialer_http_date_parse(Efl_Class *cls EINA_UNUSED, void *cd EINA_UNUSED, const char *str)
{
EINA_SAFETY_ON_NULL_RETURN_VAL(str, 0);
return curl_getdate(str, NULL);
}
EOLIAN static char *
_efl_net_dialer_http_date_serialize(Efl_Class *cls EINA_UNUSED, void *cd EINA_UNUSED, int64_t ts)
{
static const char *const wkday[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
static const char * const month[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
char buf[128];
time_t t = ts;
struct tm *tm;
#ifdef HAVE_GMTIME_R
struct tm storage;
tm = gmtime_r(&t, &storage);
#else
tm = gmtime(&t);
#endif
EINA_SAFETY_ON_NULL_RETURN_VAL(tm, NULL);
snprintf(buf, sizeof(buf),
"%s, %02d %s %4d %02d:%02d:%02d GMT",
wkday[tm->tm_wday],
tm->tm_mday,
month[tm->tm_mon],
tm->tm_year + 1900,
tm->tm_hour,
tm->tm_min,
tm->tm_sec);
return strdup(buf);
}
CURL *
efl_net_dialer_http_curl_get(const Eo *o)
{
Efl_Net_Dialer_Http_Data *pd = efl_data_scope_get(o, MY_CLASS);
EINA_SAFETY_ON_NULL_RETURN_VAL(pd, NULL);
return pd->easy;
}
#include "efl_net_dialer_http.eo.c"