Coverage Report

Created: 2026-08-13 07:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/curl_share.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 "multiif.h"
28
#include "curl_threads.h"
29
#include "curl_share.h"
30
#include "vtls/vtls.h"
31
#include "vtls/vtls_scache.h"
32
#include "hsts.h"
33
#include "url.h"
34
35
static void share_destroy(struct Curl_share *share)
36
0
{
37
0
  if(!share)
38
0
    return;
39
40
0
  if(share->specifier & (1 << CURL_LOCK_DATA_CONNECT)) {
41
0
    Curl_cpool_destroy(&share->cpool);
42
0
  }
43
44
0
  Curl_dnscache_destroy(&share->dnscache);
45
46
0
#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES)
47
0
  Curl_cookie_cleanup(share->cookies);
48
0
#endif
49
50
0
#ifndef CURL_DISABLE_HSTS
51
0
  Curl_hsts_cleanup(&share->hsts);
52
0
#endif
53
54
0
#ifdef USE_SSL
55
0
  if(share->ssl_scache) {
56
0
    Curl_ssl_scache_destroy(share->ssl_scache);
57
0
    share->ssl_scache = NULL;
58
0
  }
59
0
#endif
60
61
0
  Curl_psl_destroy(&share->psl);
62
0
  Curl_close(&share->admin);
63
64
0
#ifdef USE_MUTEX
65
0
  Curl_mutex_destroy(&share->lock);
66
0
#endif
67
0
  curlx_memzero(share, sizeof(*share));
68
0
  curlx_free(share);
69
0
}
70
71
CURLSH *curl_share_init(void)
72
0
{
73
0
  struct Curl_share *share = curlx_calloc(1, sizeof(struct Curl_share));
74
0
  if(share) {
75
0
    share->magic = CURL_GOOD_SHARE;
76
0
    share->specifier |= (1 << CURL_LOCK_DATA_SHARE);
77
0
#ifdef USE_MUTEX
78
0
    Curl_mutex_init(&share->lock);
79
0
#endif
80
0
    share->ref_count = 1;
81
0
    Curl_dnscache_init(&share->dnscache, 23);
82
0
    share->admin = curl_easy_init();
83
0
    if(!share->admin) {
84
0
      share_destroy(share);
85
0
      return NULL;
86
0
    }
87
    /* admin handles have mid 0 */
88
0
    share->admin->mid = 0;
89
0
    share->admin->state.internal = TRUE;
90
0
#ifdef DEBUGBUILD
91
0
    if(getenv("CURL_DEBUG"))
92
0
      share->admin->set.verbose = TRUE;
93
0
#endif
94
0
  }
95
0
  return share;
96
0
}
97
98
static uint32_t share_ref_inc(struct Curl_share *share)
99
0
{
100
0
  uint32_t n;
101
0
#ifdef USE_MUTEX
102
0
  Curl_mutex_acquire(&share->lock);
103
0
  n = ++(share->ref_count);
104
0
  share->has_been_shared = TRUE;
105
0
  Curl_mutex_release(&share->lock);
106
#else
107
  n = ++(share->ref_count);
108
  share->has_been_shared = TRUE;
109
#endif
110
0
  return n;
111
0
}
112
113
static uint32_t share_ref_dec(struct Curl_share *share)
114
0
{
115
0
  uint32_t n;
116
0
#ifdef USE_MUTEX
117
0
  Curl_mutex_acquire(&share->lock);
118
0
  DEBUGASSERT(share->ref_count);
119
0
  n = --(share->ref_count);
120
0
  Curl_mutex_release(&share->lock);
121
#else
122
  n = --(share->ref_count);
123
#endif
124
0
  return n;
125
0
}
126
127
static bool share_has_been_shared(struct Curl_share *share)
128
0
{
129
0
  bool was_shared;
130
0
#ifdef USE_MUTEX
131
0
  Curl_mutex_acquire(&share->lock);
132
0
  was_shared = share->has_been_shared;
133
0
  Curl_mutex_release(&share->lock);
134
#else
135
  was_shared = share->has_been_shared;
136
#endif
137
0
  return was_shared;
138
0
}
139
140
static bool share_lock_acquire(struct Curl_share *share,
141
                               struct Curl_easy *data)
142
0
{
143
0
  if(share->lockfunc && share->unlockfunc &&
144
0
     (data || share_has_been_shared(share))) {
145
0
    share->lockfunc(data, CURL_LOCK_DATA_SHARE, CURL_LOCK_ACCESS_SINGLE,
146
0
                    share->clientdata);
147
0
    return TRUE;
148
0
  }
149
0
  return FALSE;
150
0
}
151
152
static void share_lock_release(struct Curl_share *share,
153
                               struct Curl_easy *data,
154
                               bool locked)
155
0
{
156
0
  if(locked) {
157
0
    DEBUGASSERT(share->unlockfunc);
158
0
    if(share->unlockfunc)
159
0
      share->unlockfunc(data, CURL_LOCK_DATA_SHARE, share->clientdata);
160
0
  }
161
0
}
162
163
static bool share_in_use(struct Curl_share *share)
164
0
{
165
0
  bool in_use;
166
0
#ifdef USE_MUTEX
167
0
  Curl_mutex_acquire(&share->lock);
168
0
  in_use = (share->ref_count > 1);
169
0
  Curl_mutex_release(&share->lock);
170
#else
171
  bool locked = share_lock_acquire(share, NULL);
172
  in_use = (share->ref_count > 1);
173
  share_lock_release(share, NULL, locked);
174
#endif
175
0
  return in_use;
176
0
}
177
178
static void share_unlink(struct Curl_share **pshare,
179
                         struct Curl_easy *data,
180
                         bool locked)
181
0
{
182
0
  struct Curl_share *share = *pshare;
183
0
  uint32_t n;
184
185
0
  *pshare = NULL;
186
0
  n = share_ref_dec(share);
187
0
  if(locked)
188
0
    share_lock_release(share, data, locked);
189
0
  if(!n)  /* last reference gone */
190
0
    share_destroy(share);
191
0
}
192
193
#undef curl_share_setopt
194
CURLSHcode curl_share_setopt(CURLSH *sh, CURLSHoption option, ...)
195
0
{
196
0
  va_list param;
197
0
  int type;
198
0
  curl_lock_function lockfunc;
199
0
  curl_unlock_function unlockfunc;
200
0
  void *ptr;
201
0
  CURLSHcode res = CURLSHE_OK;
202
0
  struct Curl_share *share = sh;
203
204
0
  if(!GOOD_SHARE_HANDLE(share))
205
0
    return CURLSHE_INVALID;
206
207
0
  if(share_in_use(share)) {
208
    /* do not allow setting options while one or more handles are already
209
       using this share */
210
0
    return CURLSHE_IN_USE;
211
0
  }
212
213
0
  va_start(param, option);
214
215
0
  switch(option) {
216
0
  case CURLSHOPT_SHARE:
217
    /* this is a type this share will share */
218
0
    type = va_arg(param, int);
219
220
0
    switch(type) {
221
0
    case CURL_LOCK_DATA_DNS:
222
0
      break;
223
224
0
    case CURL_LOCK_DATA_COOKIE:
225
0
#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES)
226
0
      if(!share->cookies) {
227
0
        share->cookies = Curl_cookie_init();
228
0
        if(!share->cookies)
229
0
          res = CURLSHE_NOMEM;
230
0
      }
231
#else /* CURL_DISABLE_HTTP || CURL_DISABLE_COOKIES */
232
      res = CURLSHE_NOT_BUILT_IN;
233
#endif
234
0
      break;
235
236
0
    case CURL_LOCK_DATA_HSTS:
237
0
#ifndef CURL_DISABLE_HSTS
238
0
      if(!share->hsts) {
239
0
        share->hsts = Curl_hsts_init();
240
0
        if(!share->hsts)
241
0
          res = CURLSHE_NOMEM;
242
0
      }
243
#else /* CURL_DISABLE_HSTS */
244
      res = CURLSHE_NOT_BUILT_IN;
245
#endif
246
0
      break;
247
248
0
    case CURL_LOCK_DATA_SSL_SESSION:
249
0
#ifdef USE_SSL
250
0
      if(!share->ssl_scache) {
251
        /* There is no way (yet) for the application to configure the
252
         * session cache size, shared between many transfers. As for curl
253
         * itself, a high session count will impact startup time. Also, the
254
         * scache is not optimized for several hundreds of peers.
255
         * Keep it at a reasonable level. */
256
0
        if(Curl_ssl_scache_create(25, 2, &share->ssl_scache))
257
0
          res = CURLSHE_NOMEM;
258
0
      }
259
#else
260
      res = CURLSHE_NOT_BUILT_IN;
261
#endif
262
0
      break;
263
264
0
    case CURL_LOCK_DATA_CONNECT:
265
      /* It is safe to set this option several times on a share. */
266
0
      if(!share->cpool.initialized) {
267
0
        Curl_cpool_init(&share->cpool, share->admin, share, 103);
268
0
      }
269
0
      break;
270
271
0
    case CURL_LOCK_DATA_PSL:
272
0
#ifndef USE_LIBPSL
273
0
      res = CURLSHE_NOT_BUILT_IN;
274
0
#endif
275
0
      break;
276
277
0
    default:
278
0
      res = CURLSHE_BAD_OPTION;
279
0
    }
280
0
    if(!res)
281
0
      share->specifier |= (unsigned int)(1 << type);
282
0
    break;
283
284
0
  case CURLSHOPT_UNSHARE:
285
    /* this is a type this share will no longer share */
286
0
    type = va_arg(param, int);
287
0
    switch(type) {
288
0
    case CURL_LOCK_DATA_DNS:
289
0
      break;
290
291
0
    case CURL_LOCK_DATA_COOKIE:
292
0
#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES)
293
0
      if(share->cookies) {
294
0
        Curl_cookie_cleanup(share->cookies);
295
0
        share->cookies = NULL;
296
0
      }
297
#else /* CURL_DISABLE_HTTP || CURL_DISABLE_COOKIES */
298
      res = CURLSHE_NOT_BUILT_IN;
299
#endif
300
0
      break;
301
302
0
    case CURL_LOCK_DATA_HSTS:
303
0
#ifndef CURL_DISABLE_HSTS
304
0
      if(share->hsts) {
305
0
        Curl_hsts_cleanup(&share->hsts);
306
0
      }
307
#else /* CURL_DISABLE_HSTS */
308
      res = CURLSHE_NOT_BUILT_IN;
309
#endif
310
0
      break;
311
312
0
    case CURL_LOCK_DATA_SSL_SESSION:
313
0
#ifdef USE_SSL
314
0
      if(share->ssl_scache) {
315
0
        Curl_ssl_scache_destroy(share->ssl_scache);
316
0
        share->ssl_scache = NULL;
317
0
      }
318
#else
319
      res = CURLSHE_NOT_BUILT_IN;
320
#endif
321
0
      break;
322
323
0
    case CURL_LOCK_DATA_CONNECT:
324
0
      break;
325
326
0
    default:
327
0
      res = CURLSHE_BAD_OPTION;
328
0
      break;
329
0
    }
330
0
    if(!res)
331
0
      share->specifier &= ~(unsigned int)(1 << type);
332
0
    break;
333
334
0
  case CURLSHOPT_LOCKFUNC:
335
0
    lockfunc = va_arg(param, curl_lock_function);
336
0
    share->lockfunc = lockfunc;
337
0
    break;
338
339
0
  case CURLSHOPT_UNLOCKFUNC:
340
0
    unlockfunc = va_arg(param, curl_unlock_function);
341
0
    share->unlockfunc = unlockfunc;
342
0
    break;
343
344
0
  case CURLSHOPT_USERDATA:
345
0
    ptr = va_arg(param, void *);
346
0
    share->clientdata = ptr;
347
0
    break;
348
349
0
  default:
350
0
    res = CURLSHE_BAD_OPTION;
351
0
    break;
352
0
  }
353
354
0
  va_end(param);
355
356
0
  return res;
357
0
}
358
359
CURLSHcode curl_share_cleanup(CURLSH *sh)
360
0
{
361
0
  struct Curl_share *share = sh;
362
0
  bool locked;
363
0
  if(!GOOD_SHARE_HANDLE(share))
364
0
    return CURLSHE_INVALID;
365
366
0
  if(share_in_use(share))
367
0
    return CURLSHE_IN_USE;
368
369
0
  locked = share_lock_acquire(share, NULL);
370
0
  share_unlink(&share, NULL, locked);
371
0
  return CURLSHE_OK;
372
0
}
373
374
CURLSHcode Curl_share_lock(struct Curl_easy *data, curl_lock_data type,
375
                           curl_lock_access accesstype)
376
9.11k
{
377
9.11k
  struct Curl_share *share = data->share;
378
379
9.11k
  if(!share)
380
9.11k
    return CURLSHE_INVALID;
381
382
0
  if(share->specifier & (unsigned int)(1 << type)) {
383
0
    if(share->lockfunc) /* only call this if set! */
384
0
      share->lockfunc(data, type, accesstype, share->clientdata);
385
0
  }
386
  /* else if we do not share this, pretend successful lock */
387
388
0
  return CURLSHE_OK;
389
9.11k
}
390
391
CURLSHcode Curl_share_unlock(struct Curl_easy *data, curl_lock_data type)
392
9.11k
{
393
9.11k
  struct Curl_share *share = data->share;
394
395
9.11k
  if(!share)
396
9.11k
    return CURLSHE_INVALID;
397
398
0
  if(share->specifier & (unsigned int)(1 << type)) {
399
0
    if(share->unlockfunc) /* only call this if set! */
400
0
      share->unlockfunc(data, type, share->clientdata);
401
0
  }
402
403
0
  return CURLSHE_OK;
404
9.11k
}
405
406
CURLcode Curl_share_easy_unlink(struct Curl_easy *data)
407
7.07k
{
408
7.07k
  struct Curl_share *share = data->share;
409
410
7.07k
  if(share) {
411
0
    bool locked = share_lock_acquire(share, data);
412
413
    /* If data has a connection from this share, detach it. */
414
0
    if(data->conn && (share->specifier & (1 << CURL_LOCK_DATA_CONNECT)))
415
0
      Curl_detach_connection(data);
416
417
0
#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES)
418
0
    if(share->cookies == data->cookies)
419
0
      data->cookies = NULL;
420
0
#endif
421
422
0
#ifndef CURL_DISABLE_HSTS
423
0
    if(share->hsts == data->hsts)
424
0
      data->hsts = NULL;
425
0
#endif
426
#ifdef USE_LIBPSL
427
    if(&share->psl == data->psl)
428
      data->psl = data->multi ? &data->multi->psl : NULL;
429
#endif
430
431
0
    share_unlink(&data->share, data, locked);
432
0
  }
433
7.07k
  return CURLE_OK;
434
7.07k
}
435
436
CURLcode Curl_share_easy_link(struct Curl_easy *data,
437
                              struct Curl_share *share)
438
0
{
439
0
  if(data->share) {
440
0
    DEBUGASSERT(0);
441
0
    return CURLE_FAILED_INIT;
442
0
  }
443
444
0
  if(share) {
445
0
    bool locked = share_lock_acquire(share, data);
446
447
0
    share_ref_inc(share);
448
0
    data->share = share;
449
450
0
#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES)
451
0
    if(share->cookies) {
452
      /* use shared cookie list, first free own one if any */
453
0
      Curl_cookie_cleanup(data->cookies);
454
      /* enable cookies since we now use a share that uses cookies! */
455
0
      data->cookies = share->cookies;
456
0
    }
457
0
#endif /* CURL_DISABLE_HTTP */
458
0
#ifndef CURL_DISABLE_HSTS
459
0
    if(share->hsts) {
460
      /* first free the private one if any */
461
0
      Curl_hsts_cleanup(&data->hsts);
462
0
      data->hsts = share->hsts;
463
0
    }
464
0
#endif
465
#ifdef USE_LIBPSL
466
    if(share->specifier & (1 << CURL_LOCK_DATA_PSL))
467
      data->psl = &share->psl;
468
#endif
469
470
    /* check for host cache not needed,
471
     * it will be done by curl_easy_perform */
472
0
    share_lock_release(share, data, locked);
473
0
  }
474
0
  return CURLE_OK;
475
0
}