| /*************************************************************************** |
| * _ _ ____ _ |
| * Project ___| | | | _ \| | |
| * / __| | | | |_) | | |
| * | (__| |_| | _ <| |___ |
| * \___|\___/|_| \_\_____| |
| * |
| * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. |
| * |
| * This software is licensed as described in the file COPYING, which |
| * you should have received as part of this distribution. The terms |
| * are also available at https://curl.se/docs/copyright.html. |
| * |
| * You may opt to use, copy, modify, merge, publish, distribute and/or sell |
| * copies of the Software, and permit persons to whom the Software is |
| * furnished to do so, under the terms of the COPYING file. |
| * |
| * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
| * KIND, either express or implied. |
| * |
| * SPDX-License-Identifier: curl |
| * |
| ***************************************************************************/ |
| #include "curl_setup.h" |
| |
| #include "urldata.h" |
| #include "api.h" |
| #include "curl_threads.h" |
| #include "multiif.h" |
| #include "vtls/vtls_scache.h" |
| |
| struct Curl_eapi_fn_props { |
| Curl_eapi_fn fn; |
| uint8_t data_is_killed; /* easy handle is killed in call */ |
| uint8_t recurse; /* may be called when another call is in progress */ |
| uint8_t no_event_cb; /* may not be called during a multi event callback */ |
| uint8_t no_scache_lock; /* may not be called with easy's vtls_scache |
| locked by current thread */ |
| }; |
| |
| static const struct Curl_eapi_fn_props eapi_fn_props[CURL_EAPI_FN_LAST] = { |
| /* function kill rec !ev !scach */ |
| { CURL_EAPI_FN_easy_cleanup, 1, 0, 0, 0 }, |
| { CURL_EAPI_FN_easy_duphandle, 0, 1, 0, 0 }, |
| { CURL_EAPI_FN_easy_getinfo, 0, 1, 0, 0 }, |
| { CURL_EAPI_FN_easy_header, 0, 1, 0, 0 }, |
| { CURL_EAPI_FN_easy_nextheader, 0, 1, 0, 0 }, |
| { CURL_EAPI_FN_easy_pause, 0, 1, 1, 0 }, |
| { CURL_EAPI_FN_easy_perform_ev, 0, 0, 0, 1 }, |
| { CURL_EAPI_FN_easy_perform, 0, 0, 0, 1 }, |
| { CURL_EAPI_FN_easy_recv, 0, 0, 0, 1 }, |
| { CURL_EAPI_FN_easy_reset, 0, 0, 0, 0 }, |
| { CURL_EAPI_FN_easy_send, 0, 0, 0, 1 }, |
| { CURL_EAPI_FN_easy_setopt, 0, 1, 0, 0 }, |
| { CURL_EAPI_FN_easy_ssls_export, 0, 0, 0, 1 }, |
| { CURL_EAPI_FN_easy_ssls_import, 0, 0, 0, 1 }, |
| { CURL_EAPI_FN_easy_upkeep, 0, 0, 0, 1 }, |
| { CURL_EAPI_FN_ws_recv, 0, 1, 0, 1 }, |
| { CURL_EAPI_FN_ws_send, 0, 1, 0, 1 }, |
| { CURL_EAPI_FN_ws_start_frame, 0, 1, 0, 0 }, |
| }; |
| |
| struct Curl_mapi_fn_props { |
| Curl_mapi_fn fn; |
| uint8_t multi_is_killed; /* multi handle is killed during call */ |
| uint8_t recurse; /* may be called when another call is in progress */ |
| uint8_t allow_ntfy_cb; /* may be called during a notify callback */ |
| uint8_t no_scache_lock; /* may not be called with multi's vtls_scache |
| locked by current thread */ |
| }; |
| |
| static const struct Curl_mapi_fn_props mapi_fn_props[CURL_MAPI_FN_LAST] = { |
| /* function kill rec ntfy !scach */ |
| { CURL_MAPI_FN_multi_add_handle, 0, 0, 1, 0 }, |
| { CURL_MAPI_FN_multi_assign, 0, 1, 1, 0 }, |
| { CURL_MAPI_FN_multi_cleanup, 1, 0, 0, 1 }, |
| { CURL_MAPI_FN_multi_fdset, 0, 0, 1, 0 }, |
| { CURL_MAPI_FN_multi_get_handles, 0, 1, 1, 0 }, |
| { CURL_MAPI_FN_multi_get_offt, 0, 1, 1, 0 }, |
| { CURL_MAPI_FN_multi_info_read, 0, 1, 1, 0 }, |
| { CURL_MAPI_FN_multi_notify_disable, 0, 1, 1, 0 }, |
| { CURL_MAPI_FN_multi_notify_enable, 0, 1, 1, 0 }, |
| { CURL_MAPI_FN_multi_perform, 0, 0, 0, 1 }, |
| { CURL_MAPI_FN_multi_poll, 0, 0, 1, 0 }, |
| { CURL_MAPI_FN_multi_remove_handle, 0, 0, 1, 0 }, |
| { CURL_MAPI_FN_multi_setopt, 0, 0, 1, 0 }, |
| { CURL_MAPI_FN_multi_socket_action, 0, 0, 0, 1 }, |
| { CURL_MAPI_FN_multi_socket_all, 0, 0, 0, 1 }, |
| { CURL_MAPI_FN_multi_socket, 0, 0, 0, 1 }, |
| { CURL_MAPI_FN_multi_timeout, 0, 0, 1, 1 }, |
| { CURL_MAPI_FN_multi_wait, 0, 0, 1, 0 }, |
| { CURL_MAPI_FN_multi_waitfds, 0, 0, 1, 0 }, |
| }; |
| |
| struct Curl_cbapi_fn_props { |
| Curl_cbapi_fn fn; |
| uint8_t is_event_cb; /* is a multi event processing callback */ |
| }; |
| |
| static const struct Curl_cbapi_fn_props |
| cbapi_fn_props[CURL_CBAPI_FN_LAST - CURL_CBAPI_FN_START] = { |
| { CURL_CBAPI_FN_easy_chunk_bgn, 0 }, |
| { CURL_CBAPI_FN_easy_chunk_end, 0 }, |
| { CURL_CBAPI_FN_easy_closesocket, 0 }, |
| { CURL_CBAPI_FN_easy_cr_in_read, 0 }, |
| { CURL_CBAPI_FN_easy_cr_in_resume_from, 0 }, |
| { CURL_CBAPI_FN_easy_cw_out_cb, 0 }, |
| { CURL_CBAPI_FN_easy_fdebug, 0 }, |
| { CURL_CBAPI_FN_easy_fnmatch_data, 0 }, |
| { CURL_CBAPI_FN_easy_fopensocket, 0 }, |
| { CURL_CBAPI_FN_easy_fprereq, 0 }, |
| { CURL_CBAPI_FN_easy_fprogress, 0 }, |
| { CURL_CBAPI_FN_easy_fread_func, 0 }, |
| { CURL_CBAPI_FN_easy_fsockopt, 0 }, |
| { CURL_CBAPI_FN_easy_fsslctx, 0 }, |
| { CURL_CBAPI_FN_easy_fwrite_rtp, 0 }, |
| { CURL_CBAPI_FN_easy_fxferinfo, 0 }, |
| { CURL_CBAPI_FN_easy_ioctl_func, 0 }, |
| { CURL_CBAPI_FN_easy_resolver_start, 0 }, |
| { CURL_CBAPI_FN_easy_seek_func, 0 }, |
| { CURL_CBAPI_FN_easy_ssh_hostkeyfunc, 0 }, |
| { CURL_CBAPI_FN_easy_ssh_keyfunc, 0 }, |
| { CURL_CBAPI_FN_easy_trailer_callback, 0 }, |
| |
| { CURL_CBAPI_FN_multi_ntfy_cb, 0 }, |
| { CURL_CBAPI_FN_multi_push_cb, 0 }, |
| { CURL_CBAPI_FN_multi_socket_cb, 1 }, |
| { CURL_CBAPI_FN_multi_timer_cb, 1 }, |
| }; |
| |
| static bool eapi_in_event_cb(struct Curl_easy *data) |
| { |
| struct Curl_multi *multi = data->multi; |
| if(multi && multi->callstack.count) { |
| size_t i; |
| for(i = 0; i < multi->callstack.count; ++i) { |
| if(multi->callstack.calls[i] >= CURL_CBAPI_FN_START) { |
| uint16_t fn = multi->callstack.calls[i]; |
| if((fn < CURL_CBAPI_FN_LAST) && |
| cbapi_fn_props[fn - CURL_CBAPI_FN_START].is_event_cb) |
| return TRUE; |
| } |
| } |
| } |
| return FALSE; |
| } |
| |
| static bool mapi_in_ntfy_cb(struct Curl_multi *multi) |
| { |
| if(multi && multi->callstack.count) { |
| size_t i; |
| for(i = 0; i < multi->callstack.count; ++i) { |
| if(multi->callstack.calls[i] == CURL_CBAPI_FN_multi_ntfy_cb) |
| return TRUE; |
| } |
| } |
| return FALSE; |
| } |
| |
| bool Curl_api_multi_is_in_callback(struct Curl_multi *multi) |
| { |
| if(multi && multi->callstack.count) { |
| size_t i; |
| for(i = 0; i < multi->callstack.count; ++i) { |
| if(multi->callstack.calls[i] >= CURL_CBAPI_FN_START) |
| return TRUE; |
| } |
| } |
| return FALSE; |
| } |
| |
| bool Curl_api_is_in_callback(struct Curl_easy *data) |
| { |
| if(data && data->multi) { |
| return Curl_api_multi_is_in_callback(data->multi); |
| } |
| return FALSE; |
| } |
| |
| bool Curl_eapi_enter(struct Curl_eapi_guard *guard, |
| CURL *curl, |
| Curl_eapi_fn fn, |
| CURLcode *presult) |
| { |
| struct Curl_easy *data = curl; |
| const struct Curl_eapi_fn_props *fn_props; |
| CURLcode result = CURLE_OK; |
| |
| guard->depth = 0; |
| |
| /* Verify that we got an easy handle we can work with. */ |
| if(!GOOD_EASY_HANDLE(data)) { |
| result = CURLE_BAD_FUNCTION_ARGUMENT; |
| goto out; |
| } |
| /* verify that is either not added to a multi handle OR has a |
| * GOOD multi handle that knows `data` for `data->mid`. */ |
| if(data->mid != UINT32_MAX) { |
| if(GOOD_MULTI_HANDLE(data->multi)) { |
| if(!Curl_multi_knows_easy(data->multi, data)) { |
| /* But multi does not know it, something is fishy, better deny call */ |
| DEBUGASSERT(0); |
| result = CURLE_BAD_FUNCTION_ARGUMENT; |
| goto out; |
| } |
| } |
| else { |
| DEBUGASSERT(0); /* data needs to have a GOOD multi handle */ |
| result = CURLE_BAD_FUNCTION_ARGUMENT; |
| goto out; |
| } |
| } |
| else if(data->multi) { |
| DEBUGASSERT(0); /* data should not have a multi handle */ |
| result = CURLE_BAD_FUNCTION_ARGUMENT; |
| goto out; |
| } |
| /* verify that the call `fn` we're about to enter is known |
| * and check call properties to be admitting. */ |
| if(fn >= CURL_EAPI_FN_LAST) { |
| result = CURLE_BAD_FUNCTION_ARGUMENT; |
| goto out; |
| } |
| fn_props = &eapi_fn_props[fn]; |
| DEBUGASSERT(fn_props->fn == fn); |
| if(!fn_props->recurse) { |
| if(data->callstack.count) { |
| #ifdef CURLVERBOSE |
| DEBUGF(curl_mfprintf(stderr, |
| "EAPI guard: calling %hu with call to %u ongoing\n", (uint16_t)fn, |
| data->callstack.calls[data->callstack.count-1])); |
| #endif |
| result = CURLE_RECURSIVE_API_CALL; |
| goto out; |
| } |
| if(data->multi && data->multi->callstack.count) { |
| #ifdef CURLVERBOSE |
| |
| DEBUGF(curl_mfprintf(stderr, |
| "EAPI guard: calling %hu with multi call to %u ongoing\n", |
| (uint16_t)fn, |
| data->multi->callstack.calls[data->multi->callstack.count-1])); |
| #endif |
| result = CURLE_RECURSIVE_API_CALL; |
| goto out; |
| } |
| } |
| |
| if(fn_props->no_event_cb && eapi_in_event_cb(data)) { |
| /* Not allowed to be invoked while an event cb is ongoing */ |
| #ifdef CURLVERBOSE |
| DEBUGF(curl_mfprintf(stderr, |
| "EAPI guard: calling %hu while event callback ongoing\n", |
| (uint16_t)fn)); |
| #endif |
| result = CURLE_RECURSIVE_API_CALL; |
| goto out; |
| } |
| |
| #if defined(USE_SSL) && defined(USE_MUTEX) |
| if(fn_props->no_scache_lock && |
| Curl_ssl_scache_is_locked_by_current_thread(data)) { |
| #ifdef CURLVERBOSE |
| DEBUGF(curl_mfprintf(stderr, |
| "EAPI guard: calling %hu while vtls_scache is locked by " |
| "current thread\n", (uint16_t)fn)); |
| #endif |
| result = CURLE_RECURSIVE_API_CALL; |
| goto out; |
| } |
| #endif |
| |
| /* all fine, add to data's callstack */ |
| if(data->callstack.count >= CURL_EAPI_MAX_RECURSION) { |
| result = CURLE_RECURSIVE_API_CALL; |
| goto out; |
| } |
| data->callstack.calls[data->callstack.count] = (uint16_t)fn; |
| ++data->callstack.count; |
| guard->depth = data->callstack.count; |
| guard->data = fn_props->data_is_killed ? NULL : data; |
| |
| out: |
| if(presult) |
| *presult = result; |
| return guard->depth > 0; |
| } |
| |
| void Curl_eapi_leave(struct Curl_eapi_guard *guard) |
| { |
| if(guard->depth) { |
| /* guard->data is set when handle is supposed to stay alive during call */ |
| if(guard->data && GOOD_EASY_HANDLE(guard->data)) { |
| if(guard->depth > guard->data->callstack.count) { |
| DEBUGASSERT(0); /* something very wrong */ |
| } |
| else { |
| if(guard->depth < guard->data->callstack.count) { |
| DEBUGASSERT(0); /* someone forgot to clean up */ |
| } |
| /* reset to depth the guard was entered in */ |
| guard->data->callstack.count = (uint16_t)(guard->depth - 1); |
| } |
| } |
| } |
| } |
| |
| CURLHcode Curl_eapi_hcode(CURLcode result) |
| { |
| switch(result) { |
| case CURLE_OK: |
| return CURLHE_OK; |
| case CURLE_BAD_FUNCTION_ARGUMENT: |
| return CURLHE_BAD_ARGUMENT; |
| case CURLE_OUT_OF_MEMORY: |
| return CURLHE_OUT_OF_MEMORY; |
| case CURLE_NOT_BUILT_IN: |
| return CURLHE_NOT_BUILT_IN; |
| default: |
| /* Unfortunately, we cannot convert RECURSIVE_API_CALL, |
| * but since the header API is reentrant, this should not happen. */ |
| return CURLHE_BAD_ARGUMENT; |
| } |
| } |
| |
| bool Curl_mapi_enter(struct Curl_mapi_guard *guard, |
| CURLM *m, |
| Curl_mapi_fn fn, |
| CURLMcode *pmresult) |
| { |
| struct Curl_multi *multi = m; |
| const struct Curl_mapi_fn_props *fn_props; |
| CURLMcode mresult = CURLM_OK; |
| |
| guard->depth = 0; |
| |
| /* Verify that we got an easy handle we can work with. */ |
| if(!GOOD_MULTI_HANDLE(multi)) { |
| mresult = CURLM_BAD_HANDLE; |
| goto out; |
| } |
| if(fn >= CURL_MAPI_FN_LAST) { |
| mresult = CURLM_BAD_FUNCTION_ARGUMENT; |
| goto out; |
| } |
| fn_props = &mapi_fn_props[fn]; |
| DEBUGASSERT(fn_props->fn == fn); |
| if(fn_props->allow_ntfy_cb && mapi_in_ntfy_cb(multi)) { |
| /* explicitly allowed, even though normal recursion may not */ |
| } |
| else if(!fn_props->recurse && multi->callstack.count) { |
| #ifdef CURLVERBOSE |
| DEBUGF(curl_mfprintf(stderr, |
| "MAPI guard: calling %hu with call to %u ongoing\n", (uint16_t)fn, |
| multi->callstack.calls[multi->callstack.count-1])); |
| #endif |
| mresult = CURLM_RECURSIVE_API_CALL; |
| goto out; |
| } |
| |
| #if defined(USE_SSL) && defined(USE_MUTEX) |
| if(fn_props->no_scache_lock && multi->ssl_scache && |
| Curl_ssl_scache_is_locked_by_current_thread(multi->admin)) { |
| #ifdef CURLVERBOSE |
| DEBUGF(curl_mfprintf(stderr, |
| "MAPI guard: calling %hu while its vtls_scache is locked by " |
| "current thread\n", (uint16_t)fn)); |
| #endif |
| mresult = CURLM_RECURSIVE_API_CALL; |
| goto out; |
| } |
| #endif |
| |
| /* all fine, add to data's callstack */ |
| if(multi->callstack.count >= CURL_MAPI_MAX_RECURSION) { |
| mresult = CURLM_RECURSIVE_API_CALL; |
| goto out; |
| } |
| multi->callstack.calls[multi->callstack.count] = (uint16_t)fn; |
| ++multi->callstack.count; |
| guard->depth = multi->callstack.count; |
| guard->multi = fn_props->multi_is_killed ? NULL : multi; |
| |
| out: |
| if(pmresult) |
| *pmresult = mresult; |
| return guard->depth > 0; |
| } |
| |
| void Curl_mapi_leave(struct Curl_mapi_guard *guard) |
| { |
| if(guard->depth) { |
| /* guard->data is set when handle is supposed to stay alive during call */ |
| if(guard->multi && GOOD_MULTI_HANDLE(guard->multi)) { |
| if(guard->depth > guard->multi->callstack.count) { |
| DEBUGASSERT(0); /* something very wrong */ |
| } |
| else { |
| if(guard->depth < guard->multi->callstack.count) { |
| DEBUGASSERT(0); /* someone forgot to clean up */ |
| } |
| /* reset to depth the guard was entered in */ |
| guard->multi->callstack.count = (uint16_t)(guard->depth - 1); |
| } |
| } |
| } |
| } |
| |
| void Curl_cbapi_enter(struct Curl_mapi_guard *guard, |
| struct Curl_easy *data, |
| struct Curl_multi *multi, |
| Curl_cbapi_fn fn) |
| { |
| guard->depth = 0; |
| |
| if(!multi) |
| multi = data ? data->multi : NULL; |
| /* if not multi is involved here, just leave */ |
| if(!multi) |
| return; |
| /* invalid callback specifier? */ |
| if((fn >= CURL_CBAPI_FN_LAST) || (fn < CURL_CBAPI_FN_START)) { |
| DEBUGASSERT(0); |
| return; |
| } |
| DEBUGASSERT(cbapi_fn_props[fn - CURL_CBAPI_FN_START].fn == fn); |
| if(multi->callstack.count) { |
| size_t i; |
| for(i = multi->callstack.count; i; --i) { |
| if(multi->callstack.calls[i - 1] == fn) { |
| /* recursive invocation of the same callback */ |
| DEBUGASSERT(0); |
| return; |
| } |
| } |
| } |
| |
| /* all fine, add to data's callstack */ |
| /* if multi callstack already at max depth, leave */ |
| if(multi->callstack.count >= CURL_MAPI_MAX_RECURSION) |
| return; |
| multi->callstack.calls[multi->callstack.count] = (uint16_t)fn; |
| ++multi->callstack.count; |
| guard->depth = multi->callstack.count; |
| guard->multi = multi; |
| } |
| |
| void Curl_cbapi_leave(struct Curl_mapi_guard *guard) |
| { |
| Curl_mapi_leave(guard); |
| } |