Coverage Report

Created: 2026-04-28 07:09

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