| /*************************************************************************** | |
| * _ _ ____ _ | |
| * 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" | |
| typedef char mapi_fn_ids_fit_uint8[ | |
| (CURL_MAPI_FN_LAST <= CURL_CBAPI_FN_START) ? 1 : -1]; | |
| typedef char cbapi_fn_ids_fit_uint8[ | |
| ((CURL_CBAPI_FN_LAST - 1) <= UINT8_MAX) ? 1 : -1]; | |
| typedef char mapi_depth_fits_uint8[ | |
| (CURL_MAPI_MAX_RECURSION <= UINT8_MAX) ? 1 : -1]; | |
| 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) { | |
| uint8_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) || data->state.internal) { | |
| 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] = (uint8_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 = (uint8_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); | |
| /* all fine, add to data's callstack */ | |
| /* if multi callstack already at max depth, leave */ | |
| if(multi->callstack.count >= CURL_MAPI_MAX_RECURSION) { | |
| DEBUGASSERT(0); | |
| return; | |
| } | |
| multi->callstack.calls[multi->callstack.count] = (uint8_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); | |
| } |