Coverage Report

Created: 2026-08-13 07:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/api.c
Line
Count
Source
1
/***************************************************************************
2
 *                                  _   _ ____  _
3
 *  Project                     ___| | | |  _ \| |
4
 *                             / __| | | | |_) | |
5
 *                            | (__| |_| |  _ <| |___
6
 *                             \___|\___/|_| \_\_____|
7
 *
8
 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9
 *
10
 * This software is licensed as described in the file COPYING, which
11
 * you should have received as part of this distribution. The terms
12
 * are also available at https://curl.se/docs/copyright.html.
13
 *
14
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15
 * copies of the Software, and permit persons to whom the Software is
16
 * furnished to do so, under the terms of the COPYING file.
17
 *
18
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19
 * KIND, either express or implied.
20
 *
21
 * SPDX-License-Identifier: curl
22
 *
23
 ***************************************************************************/
24
#include "curl_setup.h"
25
26
#include "urldata.h"
27
#include "api.h"
28
#include "curl_threads.h"
29
#include "multiif.h"
30
#include "vtls/vtls_scache.h"
31
32
struct Curl_eapi_fn_props {
33
  Curl_eapi_fn fn;
34
  uint8_t data_is_killed; /* easy handle is killed in call */
35
  uint8_t recurse;        /* may be called when another call is in progress */
36
  uint8_t no_event_cb;    /* may not be called during a multi event callback */
37
  uint8_t no_scache_lock; /* may not be called with easy's vtls_scache
38
                             locked by current thread */
39
};
40
41
static const struct Curl_eapi_fn_props eapi_fn_props[CURL_EAPI_FN_LAST] = {
42
  /* function                   kill rec !ev !scach */
43
  { CURL_EAPI_FN_easy_cleanup,     1,  0,  0,  0 },
44
  { CURL_EAPI_FN_easy_duphandle,   0,  1,  0,  0 },
45
  { CURL_EAPI_FN_easy_getinfo,     0,  1,  0,  0 },
46
  { CURL_EAPI_FN_easy_header,      0,  1,  0,  0 },
47
  { CURL_EAPI_FN_easy_nextheader,  0,  1,  0,  0 },
48
  { CURL_EAPI_FN_easy_pause,       0,  1,  1,  0 },
49
  { CURL_EAPI_FN_easy_perform_ev,  0,  0,  0,  1 },
50
  { CURL_EAPI_FN_easy_perform,     0,  0,  0,  1 },
51
  { CURL_EAPI_FN_easy_recv,        0,  0,  0,  1 },
52
  { CURL_EAPI_FN_easy_reset,       0,  0,  0,  0 },
53
  { CURL_EAPI_FN_easy_send,        0,  0,  0,  1 },
54
  { CURL_EAPI_FN_easy_setopt,      0,  1,  0,  0 },
55
  { CURL_EAPI_FN_easy_ssls_export, 0,  0,  0,  1 },
56
  { CURL_EAPI_FN_easy_ssls_import, 0,  0,  0,  1 },
57
  { CURL_EAPI_FN_easy_upkeep,      0,  0,  0,  1 },
58
  { CURL_EAPI_FN_ws_recv,          0,  1,  0,  1 },
59
  { CURL_EAPI_FN_ws_send,          0,  1,  0,  1 },
60
  { CURL_EAPI_FN_ws_start_frame,   0,  1,  0,  0 },
61
};
62
63
struct Curl_mapi_fn_props {
64
  Curl_mapi_fn fn;
65
  uint8_t multi_is_killed; /* multi handle is killed during call */
66
  uint8_t recurse;         /* may be called when another call is in progress */
67
  uint8_t allow_ntfy_cb;   /* may be called during a notify callback */
68
  uint8_t no_scache_lock;  /* may not be called with multi's vtls_scache
69
                              locked by current thread */
70
};
71
72
static const struct Curl_mapi_fn_props mapi_fn_props[CURL_MAPI_FN_LAST] = {
73
  /* function                       kill rec ntfy !scach */
74
  { CURL_MAPI_FN_multi_add_handle,     0,  0,   1,  0 },
75
  { CURL_MAPI_FN_multi_assign,         0,  1,   1,  0 },
76
  { CURL_MAPI_FN_multi_cleanup,        1,  0,   0,  1 },
77
  { CURL_MAPI_FN_multi_fdset,          0,  0,   1,  0 },
78
  { CURL_MAPI_FN_multi_get_handles,    0,  1,   1,  0 },
79
  { CURL_MAPI_FN_multi_get_offt,       0,  1,   1,  0 },
80
  { CURL_MAPI_FN_multi_info_read,      0,  1,   1,  0 },
81
  { CURL_MAPI_FN_multi_notify_disable, 0,  1,   1,  0 },
82
  { CURL_MAPI_FN_multi_notify_enable,  0,  1,   1,  0 },
83
  { CURL_MAPI_FN_multi_perform,        0,  0,   0,  1 },
84
  { CURL_MAPI_FN_multi_poll,           0,  0,   1,  0 },
85
  { CURL_MAPI_FN_multi_remove_handle,  0,  0,   1,  0 },
86
  { CURL_MAPI_FN_multi_setopt,         0,  0,   1,  0 },
87
  { CURL_MAPI_FN_multi_socket_action,  0,  0,   0,  1 },
88
  { CURL_MAPI_FN_multi_socket_all,     0,  0,   0,  1 },
89
  { CURL_MAPI_FN_multi_socket,         0,  0,   0,  1 },
90
  { CURL_MAPI_FN_multi_timeout,        0,  0,   1,  1 },
91
  { CURL_MAPI_FN_multi_wait,           0,  0,   1,  0 },
92
  { CURL_MAPI_FN_multi_waitfds,        0,  0,   1,  0 },
93
};
94
95
struct Curl_cbapi_fn_props {
96
  Curl_cbapi_fn fn;
97
  uint8_t is_event_cb;     /* is a multi event processing callback */
98
};
99
100
static const struct Curl_cbapi_fn_props
101
cbapi_fn_props[CURL_CBAPI_FN_LAST - CURL_CBAPI_FN_START] = {
102
  { CURL_CBAPI_FN_easy_chunk_bgn,             0 },
103
  { CURL_CBAPI_FN_easy_chunk_end,             0 },
104
  { CURL_CBAPI_FN_easy_closesocket,           0 },
105
  { CURL_CBAPI_FN_easy_cr_in_read,            0 },
106
  { CURL_CBAPI_FN_easy_cr_in_resume_from,     0 },
107
  { CURL_CBAPI_FN_easy_cw_out_cb,             0 },
108
  { CURL_CBAPI_FN_easy_fdebug,                0 },
109
  { CURL_CBAPI_FN_easy_fnmatch_data,          0 },
110
  { CURL_CBAPI_FN_easy_fopensocket,           0 },
111
  { CURL_CBAPI_FN_easy_fprereq,               0 },
112
  { CURL_CBAPI_FN_easy_fprogress,             0 },
113
  { CURL_CBAPI_FN_easy_fread_func,            0 },
114
  { CURL_CBAPI_FN_easy_fsockopt,              0 },
115
  { CURL_CBAPI_FN_easy_fsslctx,               0 },
116
  { CURL_CBAPI_FN_easy_fwrite_rtp,            0 },
117
  { CURL_CBAPI_FN_easy_fxferinfo,             0 },
118
  { CURL_CBAPI_FN_easy_ioctl_func,            0 },
119
  { CURL_CBAPI_FN_easy_resolver_start,        0 },
120
  { CURL_CBAPI_FN_easy_seek_func,             0 },
121
  { CURL_CBAPI_FN_easy_ssh_hostkeyfunc,       0 },
122
  { CURL_CBAPI_FN_easy_ssh_keyfunc,           0 },
123
  { CURL_CBAPI_FN_easy_trailer_callback,      0 },
124
125
  { CURL_CBAPI_FN_multi_ntfy_cb,              0 },
126
  { CURL_CBAPI_FN_multi_push_cb,              0 },
127
  { CURL_CBAPI_FN_multi_socket_cb,            1 },
128
  { CURL_CBAPI_FN_multi_timer_cb,             1 },
129
};
130
131
static bool eapi_in_event_cb(struct Curl_easy *data)
132
0
{
133
0
  struct Curl_multi *multi = data->multi;
134
0
  if(multi && multi->callstack.count) {
135
0
    size_t i;
136
0
    for(i = 0; i < multi->callstack.count; ++i) {
137
0
      if(multi->callstack.calls[i] >= CURL_CBAPI_FN_START) {
138
0
        uint16_t fn = multi->callstack.calls[i];
139
0
        if((fn < CURL_CBAPI_FN_LAST) &&
140
0
           cbapi_fn_props[fn - CURL_CBAPI_FN_START].is_event_cb)
141
0
          return TRUE;
142
0
      }
143
0
    }
144
0
  }
145
0
  return FALSE;
146
0
}
147
148
static bool mapi_in_ntfy_cb(struct Curl_multi *multi)
149
0
{
150
0
  if(multi && multi->callstack.count) {
151
0
    size_t i;
152
0
    for(i = 0; i < multi->callstack.count; ++i) {
153
0
      if(multi->callstack.calls[i] == CURL_CBAPI_FN_multi_ntfy_cb)
154
0
        return TRUE;
155
0
    }
156
0
  }
157
0
  return FALSE;
158
0
}
159
160
bool Curl_api_multi_is_in_callback(struct Curl_multi *multi)
161
0
{
162
0
  if(multi && multi->callstack.count) {
163
0
    size_t i;
164
0
    for(i = 0; i < multi->callstack.count; ++i) {
165
0
      if(multi->callstack.calls[i] >= CURL_CBAPI_FN_START)
166
0
        return TRUE;
167
0
    }
168
0
  }
169
0
  return FALSE;
170
0
}
171
172
bool Curl_api_is_in_callback(struct Curl_easy *data)
173
0
{
174
0
  if(data && data->multi) {
175
0
    return Curl_api_multi_is_in_callback(data->multi);
176
0
  }
177
0
  return FALSE;
178
0
}
179
180
bool Curl_eapi_enter(struct Curl_eapi_guard *guard,
181
                     CURL *curl,
182
                     Curl_eapi_fn fn,
183
                     CURLcode *presult)
184
0
{
185
0
  struct Curl_easy *data = curl;
186
0
  const struct Curl_eapi_fn_props *fn_props;
187
0
  CURLcode result = CURLE_OK;
188
189
0
  guard->depth = 0;
190
191
  /* Verify that we got an easy handle we can work with. */
192
0
  if(!GOOD_EASY_HANDLE(data)) {
193
0
    result = CURLE_BAD_FUNCTION_ARGUMENT;
194
0
    goto out;
195
0
  }
196
  /* verify that is either not added to a multi handle OR has a
197
   * GOOD multi handle that knows `data` for `data->mid`. */
198
0
  if(data->mid != UINT32_MAX) {
199
0
    if(GOOD_MULTI_HANDLE(data->multi)) {
200
0
      if(!Curl_multi_knows_easy(data->multi, data)) {
201
        /* But multi does not know it, something is fishy, better deny call */
202
0
        DEBUGASSERT(0);
203
0
        result = CURLE_BAD_FUNCTION_ARGUMENT;
204
0
        goto out;
205
0
      }
206
0
    }
207
0
    else {
208
0
      DEBUGASSERT(0); /* data needs to have a GOOD multi handle */
209
0
      result = CURLE_BAD_FUNCTION_ARGUMENT;
210
0
      goto out;
211
0
    }
212
0
  }
213
0
  else if(data->multi) {
214
0
    DEBUGASSERT(0); /* data should not have a multi handle */
215
0
    result = CURLE_BAD_FUNCTION_ARGUMENT;
216
0
    goto out;
217
0
  }
218
  /* verify that the call `fn` we're about to enter is known
219
   * and check call properties to be admitting. */
220
0
  if(fn >= CURL_EAPI_FN_LAST) {
221
0
    result = CURLE_BAD_FUNCTION_ARGUMENT;
222
0
    goto out;
223
0
  }
224
0
  fn_props = &eapi_fn_props[fn];
225
0
  DEBUGASSERT(fn_props->fn == fn);
226
0
  if(!fn_props->recurse) {
227
0
    if(data->callstack.count) {
228
0
#ifdef CURLVERBOSE
229
0
      DEBUGF(curl_mfprintf(stderr,
230
0
        "EAPI guard: calling %hu with call to %u ongoing\n", (uint16_t)fn,
231
0
        data->callstack.calls[data->callstack.count-1]));
232
0
#endif
233
0
      result = CURLE_RECURSIVE_API_CALL;
234
0
      goto out;
235
0
    }
236
0
    if(data->multi && data->multi->callstack.count) {
237
0
#ifdef CURLVERBOSE
238
239
0
      DEBUGF(curl_mfprintf(stderr,
240
0
        "EAPI guard: calling %hu with multi call to %u ongoing\n",
241
0
        (uint16_t)fn,
242
0
        data->multi->callstack.calls[data->multi->callstack.count-1]));
243
0
#endif
244
0
      result = CURLE_RECURSIVE_API_CALL;
245
0
      goto out;
246
0
    }
247
0
  }
248
249
0
  if(fn_props->no_event_cb && eapi_in_event_cb(data)) {
250
    /* Not allowed to be invoked while an event cb is ongoing */
251
0
#ifdef CURLVERBOSE
252
0
      DEBUGF(curl_mfprintf(stderr,
253
0
        "EAPI guard: calling %hu while event callback ongoing\n",
254
0
        (uint16_t)fn));
255
0
#endif
256
0
    result = CURLE_RECURSIVE_API_CALL;
257
0
    goto out;
258
0
  }
259
260
0
#if defined(USE_SSL) && defined(USE_MUTEX)
261
0
  if(fn_props->no_scache_lock &&
262
0
     Curl_ssl_scache_is_locked_by_current_thread(data)) {
263
0
#ifdef CURLVERBOSE
264
0
      DEBUGF(curl_mfprintf(stderr,
265
0
        "EAPI guard: calling %hu while vtls_scache is locked by "
266
0
        "current thread\n", (uint16_t)fn));
267
0
#endif
268
0
    result = CURLE_RECURSIVE_API_CALL;
269
0
    goto out;
270
0
  }
271
0
#endif
272
273
  /* all fine, add to data's callstack */
274
0
  if(data->callstack.count >= CURL_EAPI_MAX_RECURSION) {
275
0
    result = CURLE_RECURSIVE_API_CALL;
276
0
    goto out;
277
0
  }
278
0
  data->callstack.calls[data->callstack.count] = (uint16_t)fn;
279
0
  ++data->callstack.count;
280
0
  guard->depth = data->callstack.count;
281
0
  guard->data = fn_props->data_is_killed ? NULL : data;
282
283
0
out:
284
0
  if(presult)
285
0
    *presult = result;
286
0
  return guard->depth > 0;
287
0
}
288
289
void Curl_eapi_leave(struct Curl_eapi_guard *guard)
290
0
{
291
0
  if(guard->depth) {
292
    /* guard->data is set when handle is supposed to stay alive during call */
293
0
    if(guard->data && GOOD_EASY_HANDLE(guard->data)) {
294
0
      if(guard->depth > guard->data->callstack.count) {
295
0
        DEBUGASSERT(0); /* something very wrong */
296
0
      }
297
0
      else {
298
0
        if(guard->depth < guard->data->callstack.count) {
299
0
          DEBUGASSERT(0); /* someone forgot to clean up */
300
0
        }
301
        /* reset to depth the guard was entered in */
302
0
        guard->data->callstack.count = (uint16_t)(guard->depth - 1);
303
0
      }
304
0
    }
305
0
  }
306
0
}
307
308
CURLHcode Curl_eapi_hcode(CURLcode result)
309
0
{
310
0
  switch(result) {
311
0
  case CURLE_OK:
312
0
    return CURLHE_OK;
313
0
  case CURLE_BAD_FUNCTION_ARGUMENT:
314
0
    return CURLHE_BAD_ARGUMENT;
315
0
  case CURLE_OUT_OF_MEMORY:
316
0
    return CURLHE_OUT_OF_MEMORY;
317
0
  case CURLE_NOT_BUILT_IN:
318
0
    return CURLHE_NOT_BUILT_IN;
319
0
  default:
320
    /* Unfortunately, we cannot convert RECURSIVE_API_CALL,
321
     * but since the header API is reentrant, this should not happen. */
322
0
    return CURLHE_BAD_ARGUMENT;
323
0
  }
324
0
}
325
326
bool Curl_mapi_enter(struct Curl_mapi_guard *guard,
327
                     CURLM *m,
328
                     Curl_mapi_fn fn,
329
                     CURLMcode *pmresult)
330
0
{
331
0
  struct Curl_multi *multi = m;
332
0
  const struct Curl_mapi_fn_props *fn_props;
333
0
  CURLMcode mresult = CURLM_OK;
334
335
0
  guard->depth = 0;
336
337
  /* Verify that we got an easy handle we can work with. */
338
0
  if(!GOOD_MULTI_HANDLE(multi)) {
339
0
    mresult = CURLM_BAD_HANDLE;
340
0
    goto out;
341
0
  }
342
0
  if(fn >= CURL_MAPI_FN_LAST) {
343
0
    mresult = CURLM_BAD_FUNCTION_ARGUMENT;
344
0
    goto out;
345
0
  }
346
0
  fn_props = &mapi_fn_props[fn];
347
0
  DEBUGASSERT(fn_props->fn == fn);
348
0
  if(fn_props->allow_ntfy_cb && mapi_in_ntfy_cb(multi)) {
349
    /* explicitly allowed, even though normal recursion may not */
350
0
  }
351
0
  else if(!fn_props->recurse && multi->callstack.count) {
352
0
#ifdef CURLVERBOSE
353
0
      DEBUGF(curl_mfprintf(stderr,
354
0
        "MAPI guard: calling %hu with call to %u ongoing\n", (uint16_t)fn,
355
0
        multi->callstack.calls[multi->callstack.count-1]));
356
0
#endif
357
0
    mresult = CURLM_RECURSIVE_API_CALL;
358
0
    goto out;
359
0
  }
360
361
0
#if defined(USE_SSL) && defined(USE_MUTEX)
362
0
  if(fn_props->no_scache_lock && multi->ssl_scache &&
363
0
     Curl_ssl_scache_is_locked_by_current_thread(multi->admin)) {
364
0
#ifdef CURLVERBOSE
365
0
      DEBUGF(curl_mfprintf(stderr,
366
0
        "MAPI guard: calling %hu while its vtls_scache is locked by "
367
0
        "current thread\n", (uint16_t)fn));
368
0
#endif
369
0
    mresult = CURLM_RECURSIVE_API_CALL;
370
0
    goto out;
371
0
  }
372
0
#endif
373
374
  /* all fine, add to data's callstack */
375
0
  if(multi->callstack.count >= CURL_MAPI_MAX_RECURSION) {
376
0
    mresult = CURLM_RECURSIVE_API_CALL;
377
0
    goto out;
378
0
  }
379
0
  multi->callstack.calls[multi->callstack.count] = (uint16_t)fn;
380
0
  ++multi->callstack.count;
381
0
  guard->depth = multi->callstack.count;
382
0
  guard->multi = fn_props->multi_is_killed ? NULL : multi;
383
384
0
out:
385
0
  if(pmresult)
386
0
    *pmresult = mresult;
387
0
  return guard->depth > 0;
388
0
}
389
390
void Curl_mapi_leave(struct Curl_mapi_guard *guard)
391
0
{
392
0
  if(guard->depth) {
393
    /* guard->data is set when handle is supposed to stay alive during call */
394
0
    if(guard->multi && GOOD_MULTI_HANDLE(guard->multi)) {
395
0
      if(guard->depth > guard->multi->callstack.count) {
396
0
        DEBUGASSERT(0); /* something very wrong */
397
0
      }
398
0
      else {
399
0
        if(guard->depth < guard->multi->callstack.count) {
400
0
          DEBUGASSERT(0); /* someone forgot to clean up */
401
0
        }
402
        /* reset to depth the guard was entered in */
403
0
        guard->multi->callstack.count = (uint16_t)(guard->depth - 1);
404
0
      }
405
0
    }
406
0
  }
407
0
}
408
409
void Curl_cbapi_enter(struct Curl_mapi_guard *guard,
410
                      struct Curl_easy *data,
411
                      struct Curl_multi *multi,
412
                      Curl_cbapi_fn fn)
413
0
{
414
0
  guard->depth = 0;
415
416
0
  if(!multi)
417
0
    multi = data ? data->multi : NULL;
418
  /* if not multi is involved here, just leave */
419
0
  if(!multi)
420
0
    return;
421
  /* invalid callback specifier? */
422
0
  if((fn >= CURL_CBAPI_FN_LAST) || (fn < CURL_CBAPI_FN_START)) {
423
0
    DEBUGASSERT(0);
424
0
    return;
425
0
  }
426
0
  DEBUGASSERT(cbapi_fn_props[fn - CURL_CBAPI_FN_START].fn == fn);
427
0
  if(multi->callstack.count) {
428
0
    size_t i;
429
0
    for(i = multi->callstack.count; i; --i) {
430
0
      if(multi->callstack.calls[i - 1] == fn) {
431
        /* recursive invocation of the same callback */
432
0
        DEBUGASSERT(0);
433
0
        return;
434
0
      }
435
0
    }
436
0
  }
437
438
  /* all fine, add to data's callstack */
439
  /* if multi callstack already at max depth, leave */
440
0
  if(multi->callstack.count >= CURL_MAPI_MAX_RECURSION)
441
0
    return;
442
0
  multi->callstack.calls[multi->callstack.count] = (uint16_t)fn;
443
0
  ++multi->callstack.count;
444
0
  guard->depth = multi->callstack.count;
445
0
  guard->multi = multi;
446
0
}
447
448
void Curl_cbapi_leave(struct Curl_mapi_guard *guard)
449
0
{
450
0
  Curl_mapi_leave(guard);
451
0
}