Coverage Report

Created: 2026-09-14 07:04

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