Coverage Report

Created: 2026-02-14 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libwebsockets/lib/misc/cache-ttl/file.c
Line
Count
Source
1
/*
2
 * libwebsockets - small server side websockets and web server implementation
3
 *
4
 * Copyright (C) 2010 - 2021 Andy Green <andy@warmcat.com>
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a copy
7
 * of this software and associated documentation files (the "Software"), to
8
 * deal in the Software without restriction, including without limitation the
9
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10
 * sell copies of the Software, and to permit persons to whom the Software is
11
 * furnished to do so, subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in
14
 * all copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22
 * IN THE SOFTWARE.
23
 *
24
 * Implements a cache backing store compatible with netscape cookies.txt format
25
 * There is one entry per "line", and fields are tab-delimited
26
 *
27
 * We need to know the format here, because while the unique cookie tag consists
28
 * of "hostname|urlpath|cookiename", that does not appear like that in the file;
29
 * we have to go parse the fields and synthesize the corresponding tag.
30
 *
31
 * We rely on all the fields except the cookie value fitting in a 256 byte
32
 * buffer, and allow eating multiple buffers to get a huge cookie values.
33
 *
34
 * Because the cookie file is a device-wide asset, although lws will change it
35
 * from the lws thread without conflict, there may be other processes that will
36
 * change it by removal and regenerating the file asynchronously.  For that
37
 * reason, file handles are opened fresh each time we want to use the file, so
38
 * we always get the latest version.
39
 *
40
 * When updating the file ourselves, we use a lockfile to ensure our process
41
 * has exclusive access.
42
 *
43
 *
44
 * Tag Matching rules
45
 *
46
 * There are three kinds of tag matching rules
47
 *
48
 * 1) specific - tag strigs must be the same
49
 * 2) wilcard - tags matched using optional wildcards
50
 * 3) wildcard + lookup - wildcard, but path part matches using cookie scope rules
51
 *
52
 */
53
54
#include <private-lib-core.h>
55
#include "private-lib-misc-cache-ttl.h"
56
57
typedef enum nsc_iterator_ret {
58
  NIR_CONTINUE    = 0,
59
  NIR_FINISH_OK   = 1,
60
  NIR_FINISH_ERROR  = -1
61
} nsc_iterator_ret_t;
62
63
typedef enum cbreason {
64
  LCN_SOL     = (1 << 0),
65
  LCN_EOL     = (1 << 1)
66
} cbreason_t;
67
68
typedef int (*nsc_cb_t)(lws_cache_nscookiejar_t *cache, void *opaque, int flags,
69
      const char *buf, size_t size);
70
71
static void
72
expiry_cb(lws_sorted_usec_list_t *sul);
73
74
static int
75
nsc_backing_open_lock(lws_cache_nscookiejar_t *cache, int mode, const char *par)
76
0
{
77
0
  int sanity = 50;
78
0
  char lock[128];
79
0
  int fd_lock, fd;
80
81
0
  lwsl_debug("%s: %s\n", __func__, par);
82
83
0
  lws_snprintf(lock, sizeof(lock), "%s.LCK",
84
0
      cache->cache.info.u.nscookiejar.filepath);
85
86
0
  do {
87
0
    fd_lock = open(lock, LWS_O_CREAT | O_EXCL, 0600);
88
0
    if (fd_lock >= 0) {
89
0
      close(fd_lock);
90
0
      break;
91
0
    }
92
93
0
    if (!sanity--) {
94
0
      lwsl_warn("%s: unable to lock %s: errno %d\n", __func__,
95
0
          lock, errno);
96
0
      return -1;
97
0
    }
98
99
#if defined(WIN32)
100
    Sleep(100);
101
#else
102
0
    usleep(100000);
103
0
#endif
104
0
  } while (1);
105
106
0
  fd = open(cache->cache.info.u.nscookiejar.filepath,
107
0
          LWS_O_CREAT | mode, 0600);
108
109
0
  if (fd == -1) {
110
0
    lwsl_warn("%s: unable to open or create %s\n", __func__,
111
0
        cache->cache.info.u.nscookiejar.filepath);
112
0
    unlink(lock);
113
0
  }
114
115
0
  return fd;
116
0
}
117
118
static void
119
nsc_backing_close_unlock(lws_cache_nscookiejar_t *cache, int fd)
120
0
{
121
0
  char lock[128];
122
123
0
  lwsl_debug("%s\n", __func__);
124
125
0
  lws_snprintf(lock, sizeof(lock), "%s.LCK",
126
0
      cache->cache.info.u.nscookiejar.filepath);
127
0
  if (fd >= 0)
128
0
    close(fd);
129
0
  unlink(lock);
130
0
}
131
132
/*
133
 * We're going to call the callback with chunks of the file with flags
134
 * indicating we're giving it the start of a line and / or giving it the end
135
 * of a line.
136
 *
137
 * It's like this because the cookie value may be huge (and to a lesser extent
138
 * the path may also be big).
139
 *
140
 * If it's the start of a line (flags on the cb has LCN_SOL), then the buffer
141
 * contains up to the first 256 chars of the line, it's enough to match with.
142
 *
143
 * We cannot hold the file open inbetweentimes, since other processes may
144
 * regenerate it, so we need to bind to a new inode.  We open it with an
145
 * exclusive flock() so other processes can't replace conflicting changes
146
 * while we also write changes, without having to wait and see our changes.
147
 */
148
149
static int
150
nscookiejar_iterate(lws_cache_nscookiejar_t *cache, int fd,
151
        nsc_cb_t cb, void *opaque)
152
0
{
153
#if defined(__COVERITY__)
154
  return -1;
155
#else
156
0
  int m = 0, n = 0, e, r = LCN_SOL, ignore = 0, ret = 0;
157
0
  char temp[256], eof = 0;
158
159
0
  if (lseek(fd, 0, SEEK_SET) == (off_t)-1)
160
0
    return -1;
161
162
0
  do { /* for as many buffers in the file */
163
0
    ssize_t n1s; /* coverity taints if we use int cast here */
164
165
0
    lwsl_debug("%s: n %d, m %d\n", __func__, n, m);
166
167
0
read:
168
0
    if ((size_t)n >= sizeof(temp) - 1)
169
      /* there's no space left in temp */
170
0
      n1s = 0;
171
0
    else
172
      /*
173
       * Coverity says:  "The expression 256UL - (size_t)n is
174
       * deemed underflowed because at least one of its
175
       * arguments has underflowed." ... however we explicitly
176
       * check if n >= 256 a couple of lines above.
177
       * n cannot be negative either.
178
       *
179
       * Removing this function from Coverity
180
       */
181
0
      n1s = read(fd, temp + n, sizeof(temp) - (size_t)n);
182
183
0
    lwsl_debug("%s: n1 %d\n", __func__, (int)n1s);
184
185
0
    if (n1s <= 0) {
186
0
      eof = 1;
187
0
      if (m == n)
188
0
        continue;
189
0
    } else {
190
      /*
191
       * Help coverity see we cannot overflow n here
192
       */
193
0
      if ((size_t)n >= sizeof(temp) ||
194
0
          (size_t)n1s >= sizeof(temp) ||
195
0
          (size_t)(n + n1s) >= sizeof(temp)) {
196
0
        ret = -1;
197
0
        goto bail;
198
0
      }
199
200
0
      n = (int)(n + n1s);
201
0
    }
202
203
0
    while (m < n) {
204
205
0
      m++; /* m can == n now then */
206
207
0
      if (temp[m - 1] != '\n')
208
0
        continue;
209
210
      /* ie, we hit EOL */
211
212
0
      if (temp[0] == '#')
213
        /* lines starting with # are comments */
214
0
        e = 0;
215
0
      else
216
0
        e = cb(cache, opaque, r | LCN_EOL, temp,
217
0
               (size_t)m - 1);
218
0
      r = LCN_SOL;
219
0
      ignore = 0;
220
      /*
221
       * Move back remainder and prefill the gap that opened
222
       * up: we want to pass enough in the start chunk so the
223
       * cb can classify it even if it can't get all the
224
       * value part in one go
225
       */
226
227
      /* coverity: we will blow up if m > n */
228
0
      if (m > n) {
229
0
        ret = -1;
230
0
        goto bail;
231
0
      }
232
233
0
      memmove(temp, temp + m, (size_t)(n - m));
234
0
      n -= m;
235
0
      m = 0;
236
237
0
      if (e) {
238
0
        ret = e;
239
0
        goto bail;
240
0
      }
241
242
0
      goto read;
243
0
    }
244
245
0
    if (m) {
246
      /* we ran out of buffer */
247
0
      if (ignore || (r == LCN_SOL && n && temp[0] == '#')) {
248
0
        e = 0;
249
0
        ignore = 1;
250
0
      } else {
251
0
        e = cb(cache, opaque,
252
0
               r | (n == m && eof ? LCN_EOL : 0),
253
0
               temp, (size_t)m);
254
255
0
        m = 0;
256
0
        n = 0;
257
0
      }
258
259
0
      if (e) {
260
        /*
261
         * We have to call off the whole thing if any
262
         * step, eg, OOMs
263
         */
264
0
        ret = e;
265
0
        goto bail;
266
0
      }
267
0
      r = 0;
268
0
    }
269
270
0
  } while (!eof || n != m);
271
272
0
  ret = 0;
273
274
0
bail:
275
276
0
  return ret;
277
0
#endif
278
0
}
279
280
/*
281
 * lookup() just handles wildcard resolution, it doesn't deal with moving the
282
 * hits to L1.  That has to be done individually by non-wildcard names.
283
 */
284
285
enum {
286
  NSC_COL_HOST    = 0, /* wc idx 0 */
287
  NSC_COL_PATH    = 2, /* wc idx 1 */
288
  NSC_COL_EXPIRY    = 4,
289
  NSC_COL_NAME    = 5, /* wc idx 2 */
290
291
  NSC_COL_COUNT   = 6
292
};
293
294
/*
295
 * This performs the specialized wildcard that knows about cookie path match
296
 * rules.
297
 *
298
 * To defeat the lookup path matching, lie to it about idx being NSC_COL_PATH
299
 */
300
301
static int
302
nsc_match(const char *wc, size_t wc_len, const char *col, size_t col_len,
303
    int idx)
304
0
{
305
0
  size_t n = 0;
306
307
0
  if (idx != NSC_COL_PATH)
308
0
    return lws_strcmp_wildcard(wc, wc_len, col, col_len);
309
310
  /*
311
   * Cookie path match is special, if we lookup on a path like /my/path,
312
   * we must match on cookie paths for every dir level including /, so
313
   * match on /, /my, and /my/path.  But we must not match on /m or
314
   * /my/pa etc.  If we lookup on /, we must not match /my/path
315
   *
316
   * Let's go through wc checking at / and for every complete subpath if
317
   * it is an explicit match
318
   */
319
320
0
  if (!strcmp(col, wc))
321
0
    return 0; /* exact hit */
322
323
0
  while (n <= wc_len) {
324
0
    if (n == wc_len || wc[n] == '/') {
325
0
      if (n && col_len <= n && !strncmp(wc, col, n))
326
0
        return 0; /* hit */
327
328
0
      if (n != wc_len && col_len <= n + 1 &&
329
0
          !strncmp(wc, col, n + 1)) /* check for trailing / */
330
0
        return 0; /* hit */
331
0
    }
332
0
    n++;
333
0
  }
334
335
0
  return 1; /* fail */
336
0
}
337
338
static const uint8_t nsc_cols[] = { NSC_COL_HOST, NSC_COL_PATH, NSC_COL_NAME };
339
340
static int
341
lws_cache_nscookiejar_tag_match(struct lws_cache_ttl_lru *cache,
342
        const char *wc, const char *tag, char lookup)
343
0
{
344
0
  const char *wc_end = wc + strlen(wc), *tag_end = tag + strlen(tag),
345
0
      *start_wc, *start_tag;
346
0
  int n = 0;
347
348
0
  lwsl_cache("%s: '%s' vs '%s'\n", __func__, wc, tag);
349
350
  /*
351
   * Given a well-formed host|path|name tag and a wildcard term,
352
   * make the determination if the tag matches the wildcard or not,
353
   * using lookup rules that apply at this cache level.
354
   */
355
356
0
  while (n < 3) {
357
0
    start_wc = wc;
358
0
    while (wc < wc_end && *wc != LWSCTAG_SEP)
359
0
      wc++;
360
361
0
    start_tag = tag;
362
0
    while (tag < tag_end && *tag != LWSCTAG_SEP)
363
0
      tag++;
364
365
0
    lwsl_cache("%s:   '%.*s' vs '%.*s'\n", __func__,
366
0
        lws_ptr_diff(wc, start_wc), start_wc,
367
0
        lws_ptr_diff(tag, start_tag), start_tag);
368
0
    if (nsc_match(start_wc, lws_ptr_diff_size_t(wc, start_wc),
369
0
            start_tag, lws_ptr_diff_size_t(tag, start_tag),
370
0
            lookup ? nsc_cols[n] : NSC_COL_HOST)) {
371
0
      lwsl_cache("%s: fail\n", __func__);
372
0
      return 1;
373
0
    }
374
375
0
    if (wc < wc_end)
376
0
      wc++;
377
0
    if (tag < tag_end)
378
0
      tag++;
379
380
0
    n++;
381
0
  }
382
383
0
  lwsl_cache("%s: hit\n", __func__);
384
385
0
  return 0; /* match */
386
0
}
387
388
/*
389
 * Converts the start of a cookie file line into a tag
390
 */
391
392
static int
393
nsc_line_to_tag(const char *buf, size_t size, char *tag, size_t max_tag,
394
    lws_usec_t *pexpiry)
395
0
{
396
0
  int n, idx = 0, tl = 0;
397
0
  lws_usec_t expiry = 0;
398
0
  size_t bn = 0;
399
0
  char col[64];
400
401
0
  if (size < 3)
402
0
    return 1;
403
404
0
  while (bn < size && idx <= NSC_COL_NAME) {
405
406
0
    n = 0;
407
0
    while (bn < size && n < (int)sizeof(col) - 1 &&
408
0
           buf[bn] != '\t')
409
0
      col[n++] = buf[bn++];
410
0
    col[n] = '\0';
411
0
    if (buf[bn] == '\t')
412
0
      bn++;
413
414
0
    switch (idx) {
415
0
    case NSC_COL_EXPIRY:
416
0
      expiry = (lws_usec_t)((unsigned long long)atoll(col) *
417
0
          (lws_usec_t)LWS_US_PER_SEC);
418
0
      break;
419
420
0
    case NSC_COL_HOST:
421
0
    case NSC_COL_PATH:
422
0
    case NSC_COL_NAME:
423
424
      /*
425
       * As we match the pieces of the wildcard,
426
       * compose the matches into a specific tag
427
       */
428
429
0
      if (tl + n + 2 > (int)max_tag)
430
0
        return 1;
431
0
      if (tl)
432
0
        tag[tl++] = LWSCTAG_SEP;
433
0
      memcpy(tag + tl, col, (size_t)n);
434
0
      tl += n;
435
0
      tag[tl] = '\0';
436
0
      break;
437
0
    default:
438
0
      break;
439
0
    }
440
441
0
    idx++;
442
0
  }
443
444
0
  if (pexpiry)
445
0
    *pexpiry = expiry;
446
447
0
  lwsl_info("%s: %.*s: tag '%s'\n", __func__, (int)size, buf, tag);
448
449
0
  return 0;
450
0
}
451
452
struct nsc_lookup_ctx {
453
  const char    *wildcard_key;
454
  lws_dll2_owner_t  *results_owner;
455
  lws_cache_match_t *match; /* current match if any */
456
  size_t      wklen;
457
};
458
459
460
static int
461
nsc_lookup_cb(lws_cache_nscookiejar_t *cache, void *opaque, int flags,
462
        const char *buf, size_t size)
463
0
{
464
0
  struct nsc_lookup_ctx *ctx = (struct nsc_lookup_ctx *)opaque;
465
0
  lws_usec_t expiry;
466
0
  char tag[200];
467
0
  int tl;
468
469
0
  if (!(flags & LCN_SOL)) {
470
0
    if (ctx->match)
471
0
      ctx->match->payload_size += size;
472
473
0
    return NIR_CONTINUE;
474
0
  }
475
476
  /*
477
   * There should be enough in buf to match or reject it... let's
478
   * synthesize a tag from the text "line" and then check the tags for
479
   * a match
480
   */
481
482
0
  ctx->match = NULL; /* new SOL means stop tracking payload len */
483
484
0
  if (nsc_line_to_tag(buf, size, tag, sizeof(tag), &expiry))
485
0
    return NIR_CONTINUE;
486
487
0
  if (lws_cache_nscookiejar_tag_match(&cache->cache,
488
0
              ctx->wildcard_key, tag, 1))
489
0
    return NIR_CONTINUE;
490
491
0
  tl = (int)strlen(tag);
492
493
  /*
494
   * ... it looks like a match then... create new match
495
   * object with the specific tag, and add it to the owner list
496
   */
497
498
0
  ctx->match = lws_fi(&cache->cache.info.cx->fic, "cache_lookup_oom") ? NULL :
499
0
      lws_malloc(sizeof(*ctx->match) + (unsigned int)tl + 1u,
500
0
        __func__);
501
0
  if (!ctx->match)
502
    /* caller of lookup will clean results list on fail */
503
0
    return NIR_FINISH_ERROR;
504
505
0
  ctx->match->payload_size = size;
506
0
  ctx->match->tag_size = (size_t)tl;
507
0
  ctx->match->expiry = expiry;
508
509
0
  memset(&ctx->match->list, 0, sizeof(ctx->match->list));
510
0
  memcpy(&ctx->match[1], tag, (size_t)tl + 1u);
511
0
  lws_dll2_add_tail(&ctx->match->list, ctx->results_owner);
512
513
0
  return NIR_CONTINUE;
514
0
}
515
516
static int
517
lws_cache_nscookiejar_lookup(struct lws_cache_ttl_lru *_c,
518
           const char *wildcard_key,
519
           lws_dll2_owner_t *results_owner)
520
0
{
521
0
  lws_cache_nscookiejar_t *cache = (lws_cache_nscookiejar_t *)_c;
522
0
  struct nsc_lookup_ctx ctx;
523
0
  int ret, fd;
524
525
0
  fd = nsc_backing_open_lock(cache, LWS_O_RDONLY, __func__);
526
0
  if (fd < 0)
527
0
    return 1;
528
529
0
  ctx.wildcard_key = wildcard_key;
530
0
  ctx.results_owner = results_owner;
531
0
  ctx.wklen = strlen(wildcard_key);
532
0
  ctx.match = 0;
533
534
0
  ret = nscookiejar_iterate(cache, fd, nsc_lookup_cb, &ctx);
535
    /*
536
     * The cb can fail, eg, with OOM, making the whole lookup
537
     * invalid and returning fail.  Caller will clean
538
     * results_owner on fail.
539
     */
540
0
  nsc_backing_close_unlock(cache, fd);
541
542
0
  return ret == NIR_FINISH_ERROR;
543
0
}
544
545
/*
546
 * It's pretty horrible having to implement add or remove individual items by
547
 * file regeneration, but if we don't want to keep it all in heap, and we want
548
 * this cookie jar format, that is what we are into.
549
 *
550
 * Allow to optionally add a "line", optionally wildcard delete tags, and always
551
 * delete expired entries.
552
 *
553
 * Although we can rely on the lws thread to be doing this, multiple processes
554
 * may be using the cookie jar and can tread on each other.  So we use flock()
555
 * (linux only) to get exclusive access while we are processing this.
556
 *
557
 * We leave the existing file alone and generate a new one alongside it, with a
558
 * fixed name.tmp format so it can't leak, if that went OK then we unlink the
559
 * old and rename the new.
560
 */
561
562
struct nsc_regen_ctx {
563
  const char    *wildcard_key_delete;
564
  const void    *add_data;
565
  lws_usec_t    curr;
566
  size_t      add_size;
567
  int     fdt;
568
  char      drop;
569
};
570
571
/* only used by nsc_regen() */
572
573
static int
574
nsc_regen_cb(lws_cache_nscookiejar_t *cache, void *opaque, int flags,
575
        const char *buf, size_t size)
576
0
{
577
0
  struct nsc_regen_ctx *ctx = (struct nsc_regen_ctx *)opaque;
578
0
  char tag[256];
579
0
  lws_usec_t expiry;
580
581
0
  if (flags & LCN_SOL) {
582
583
0
    ctx->drop = 0;
584
585
0
    if (nsc_line_to_tag(buf, size, tag, sizeof(tag), &expiry))
586
      /* filter it out if it is unparseable */
587
0
      goto drop;
588
589
    /* routinely track the earliest expiry */
590
591
0
    if (!cache->earliest_expiry ||
592
0
        (expiry && cache->earliest_expiry > expiry))
593
0
      cache->earliest_expiry = expiry;
594
595
0
    if (expiry && expiry < ctx->curr)
596
      /* routinely strip anything beyond its expiry */
597
0
      goto drop;
598
599
0
    if (ctx->wildcard_key_delete)
600
0
      lwsl_cache("%s: %s vs %s\n", __func__,
601
0
          tag, ctx->wildcard_key_delete);
602
0
    if (ctx->wildcard_key_delete &&
603
0
        !lws_cache_nscookiejar_tag_match(&cache->cache,
604
0
                 ctx->wildcard_key_delete,
605
0
                 tag, 0)) {
606
0
      lwsl_cache("%s: %s matches wc delete %s\n", __func__,
607
0
          tag, ctx->wildcard_key_delete);
608
0
      goto drop;
609
0
    }
610
0
  }
611
612
0
  if (ctx->drop)
613
0
    return 0;
614
615
0
  cache->cache.current_footprint += (uint64_t)size;
616
617
0
  if (write(ctx->fdt, buf, /*msvc*/(unsigned int)size) != (ssize_t)size)
618
0
    return NIR_FINISH_ERROR;
619
620
0
  if (flags & LCN_EOL)
621
0
    if ((size_t)write(ctx->fdt, "\n", 1) != 1)
622
0
      return NIR_FINISH_ERROR;
623
624
0
  return 0;
625
626
0
drop:
627
0
  ctx->drop = 1;
628
629
0
  return NIR_CONTINUE;
630
0
}
631
632
static int
633
nsc_regen(lws_cache_nscookiejar_t *cache, const char *wc_delete,
634
    const void *pay, size_t pay_size)
635
0
{
636
0
  struct nsc_regen_ctx ctx;
637
0
  char filepath[128];
638
0
  int fd, ret = 1;
639
640
0
  fd = nsc_backing_open_lock(cache, LWS_O_RDONLY, __func__);
641
0
  if (fd < 0)
642
0
    return 1;
643
644
0
  lws_snprintf(filepath, sizeof(filepath), "%s.tmp",
645
0
      cache->cache.info.u.nscookiejar.filepath);
646
0
  unlink(filepath);
647
648
0
  if (lws_fi(&cache->cache.info.cx->fic, "cache_regen_temp_open"))
649
0
    goto bail;
650
651
0
  ctx.fdt = open(filepath, LWS_O_CREAT | LWS_O_WRONLY, 0600);
652
0
  if (ctx.fdt < 0)
653
0
    goto bail;
654
655
  /* magic header */
656
657
0
  if (lws_fi(&cache->cache.info.cx->fic, "cache_regen_temp_write") ||
658
  /* other consumers insist to see this at start of cookie jar */
659
0
      write(ctx.fdt, "# Netscape HTTP Cookie File\n", 28) != 28)
660
0
    goto bail1;
661
662
  /* if we are adding something, put it first */
663
664
0
  if (pay &&
665
0
      write(ctx.fdt, pay, /*msvc*/(unsigned int)pay_size) !=
666
0
                (ssize_t)pay_size)
667
0
    goto bail1;
668
0
  if (pay && write(ctx.fdt, "\n", 1u) != (ssize_t)1)
669
0
    goto bail1;
670
671
0
  cache->cache.current_footprint = 0;
672
673
0
  ctx.wildcard_key_delete = wc_delete;
674
0
  ctx.add_data = pay;
675
0
  ctx.add_size = pay_size;
676
0
  ctx.curr = lws_now_usecs();
677
0
  ctx.drop = 0;
678
679
0
  cache->earliest_expiry = 0;
680
681
0
  if (lws_fi(&cache->cache.info.cx->fic, "cache_regen_iter_fail") ||
682
0
      nscookiejar_iterate(cache, fd, nsc_regen_cb, &ctx))
683
0
    goto bail1;
684
685
0
  close(ctx.fdt);
686
0
  ctx.fdt = -1;
687
688
0
  if (unlink(cache->cache.info.u.nscookiejar.filepath) == -1)
689
0
    lwsl_info("%s: unlink %s failed\n", __func__,
690
0
        cache->cache.info.u.nscookiejar.filepath);
691
0
  if (rename(filepath, cache->cache.info.u.nscookiejar.filepath) == -1)
692
0
    lwsl_info("%s: rename %s failed\n", __func__,
693
0
        cache->cache.info.u.nscookiejar.filepath);
694
695
0
  if (cache->earliest_expiry)
696
0
    lws_cache_schedule(&cache->cache, expiry_cb,
697
0
           cache->earliest_expiry);
698
699
0
  ret = 0;
700
0
  goto bail;
701
702
0
bail1:
703
0
  if (ctx.fdt >= 0)
704
0
    close(ctx.fdt);
705
0
bail:
706
0
  unlink(filepath);
707
708
0
  nsc_backing_close_unlock(cache, fd);
709
710
0
  return ret;
711
0
}
712
713
static void
714
expiry_cb(lws_sorted_usec_list_t *sul)
715
0
{
716
0
  lws_cache_nscookiejar_t *cache = lws_container_of(sul,
717
0
          lws_cache_nscookiejar_t, cache.sul);
718
719
  /*
720
   * regen the cookie jar without changes, so expired are removed and
721
   * new earliest expired computed
722
   */
723
0
  if (nsc_regen(cache, NULL, NULL, 0))
724
0
    return;
725
726
0
  if (cache->earliest_expiry)
727
0
    lws_cache_schedule(&cache->cache, expiry_cb,
728
0
           cache->earliest_expiry);
729
0
}
730
731
732
/* specific_key and expiry are ignored, since it must be encoded in payload */
733
734
static int
735
lws_cache_nscookiejar_write(struct lws_cache_ttl_lru *_c,
736
          const char *specific_key, const uint8_t *source,
737
          size_t size, lws_usec_t expiry, void **ppvoid)
738
0
{
739
0
  lws_cache_nscookiejar_t *cache = (lws_cache_nscookiejar_t *)_c;
740
0
  char tag[128];
741
742
0
  lwsl_cache("%s: %s: len %d\n", __func__, _c->info.name, (int)size);
743
744
0
  assert(source);
745
746
0
  if (nsc_line_to_tag((const char *)source, size, tag, sizeof(tag), NULL))
747
0
    return 1;
748
749
0
  if (ppvoid)
750
0
    *ppvoid = NULL;
751
752
0
  if (nsc_regen(cache, tag, source, size)) {
753
0
    lwsl_err("%s: regen failed\n", __func__);
754
755
0
    return 1;
756
0
  }
757
758
0
  return 0;
759
0
}
760
761
struct nsc_get_ctx {
762
  struct lws_buflist  *buflist;
763
  const char    *specific_key;
764
  const void    **pdata;
765
  size_t      *psize;
766
  lws_cache_ttl_lru_t *l1;
767
  lws_usec_t    expiry;
768
};
769
770
/*
771
 * We're looking for a specific key, if found, we want to make an entry for it
772
 * in L1 and return information about that
773
 */
774
775
static int
776
nsc_get_cb(lws_cache_nscookiejar_t *cache, void *opaque, int flags,
777
     const char *buf, size_t size)
778
0
{
779
0
  struct nsc_get_ctx *ctx = (struct nsc_get_ctx *)opaque;
780
0
  char tag[200];
781
0
  uint8_t *q;
782
783
0
  if (ctx->buflist)
784
0
    goto collect;
785
786
0
  if (!(flags & LCN_SOL))
787
0
    return NIR_CONTINUE;
788
789
0
  if (nsc_line_to_tag(buf, size, tag, sizeof(tag), &ctx->expiry)) {
790
0
    lwsl_err("%s: can't get tag\n", __func__);
791
0
    return NIR_CONTINUE;
792
0
  }
793
794
0
  lwsl_cache("%s: %s %s\n", __func__, ctx->specific_key, tag);
795
796
0
  if (strcmp(ctx->specific_key, tag)) {
797
0
    lwsl_cache("%s: no match\n", __func__);
798
0
    return NIR_CONTINUE;
799
0
  }
800
801
  /* it's a match */
802
803
0
  lwsl_cache("%s: IS match\n", __func__);
804
805
0
  if (!(flags & LCN_EOL))
806
0
    goto collect;
807
808
  /* it all fit in the buffer, let's create it in L1 now */
809
810
0
  *ctx->psize = size;
811
0
  if (ctx->l1->info.ops->write(ctx->l1,
812
0
             ctx->specific_key, (const uint8_t *)buf,
813
0
             size, ctx->expiry, (void **)ctx->pdata))
814
0
    return NIR_FINISH_ERROR;
815
816
0
  return NIR_FINISH_OK;
817
818
0
collect:
819
  /*
820
   * it's bigger than one buffer-load, we have to stash what we're getting
821
   * on a buflist and create it when we have it all
822
   */
823
824
0
  if (lws_buflist_append_segment(&ctx->buflist, (const uint8_t *)buf,
825
0
               size))
826
0
    goto cleanup;
827
828
0
  if (!(flags & LCN_EOL))
829
0
    return NIR_CONTINUE;
830
831
  /* we have all the payload, create the L1 entry without payload yet */
832
833
0
  *ctx->psize = size;
834
0
  if (ctx->l1->info.ops->write(ctx->l1, ctx->specific_key, NULL,
835
0
             lws_buflist_total_len(&ctx->buflist),
836
0
             ctx->expiry, (void **)&q))
837
0
    goto cleanup;
838
0
  *ctx->pdata = q;
839
840
  /* dump the buflist into the L1 cache entry */
841
842
0
  do {
843
0
    uint8_t *p;
844
0
    size_t len = lws_buflist_next_segment_len(&ctx->buflist, &p);
845
846
0
    memcpy(q, p, len);
847
0
    q += len;
848
849
0
    lws_buflist_use_segment(&ctx->buflist, len);
850
0
  } while (ctx->buflist);
851
852
0
  return NIR_FINISH_OK;
853
854
0
cleanup:
855
0
  lws_buflist_destroy_all_segments(&ctx->buflist);
856
857
0
  return NIR_FINISH_ERROR;
858
0
}
859
860
static int
861
lws_cache_nscookiejar_get(struct lws_cache_ttl_lru *_c,
862
        const char *specific_key, const void **pdata,
863
        size_t *psize)
864
0
{
865
0
  lws_cache_nscookiejar_t *cache = (lws_cache_nscookiejar_t *)_c;
866
0
  struct nsc_get_ctx ctx;
867
0
  int ret, fd;
868
869
0
  fd = nsc_backing_open_lock(cache, LWS_O_RDONLY, __func__);
870
0
  if (fd < 0)
871
0
    return 1;
872
873
  /* get a pointer to l1 */
874
0
  ctx.l1 = &cache->cache;
875
0
  while (ctx.l1->child)
876
0
    ctx.l1 = ctx.l1->child;
877
878
0
  ctx.pdata = pdata;
879
0
  ctx.psize = psize;
880
0
  ctx.specific_key = specific_key;
881
0
  ctx.buflist = NULL;
882
0
  ctx.expiry = 0;
883
884
0
  ret = nscookiejar_iterate(cache, fd, nsc_get_cb, &ctx);
885
886
0
  nsc_backing_close_unlock(cache, fd);
887
888
0
  return ret != NIR_FINISH_OK;
889
0
}
890
891
static int
892
lws_cache_nscookiejar_invalidate(struct lws_cache_ttl_lru *_c,
893
         const char *wc_key)
894
0
{
895
0
  lws_cache_nscookiejar_t *cache = (lws_cache_nscookiejar_t *)_c;
896
897
0
  return nsc_regen(cache, wc_key, NULL, 0);
898
0
}
899
900
static struct lws_cache_ttl_lru *
901
lws_cache_nscookiejar_create(const struct lws_cache_creation_info *info)
902
0
{
903
0
  lws_cache_nscookiejar_t *cache;
904
905
0
  cache = lws_fi(&info->cx->fic, "cache_createfail") ? NULL :
906
0
          lws_zalloc(sizeof(*cache), __func__);
907
0
  if (!cache)
908
0
    return NULL;
909
910
0
  cache->cache.info = *info;
911
912
  /*
913
   * We need to scan the file, if it exists, and find the earliest
914
   * expiry while cleaning out any expired entries
915
   */
916
0
  expiry_cb(&cache->cache.sul);
917
918
0
  lwsl_info("%s: create %s\n", __func__, info->name ? info->name : "?");
919
920
0
  return (struct lws_cache_ttl_lru *)cache;
921
0
}
922
923
static int
924
lws_cache_nscookiejar_expunge(struct lws_cache_ttl_lru *_c)
925
0
{
926
0
  lws_cache_nscookiejar_t *cache = (lws_cache_nscookiejar_t *)_c;
927
0
  int r;
928
929
0
  if (!cache)
930
0
    return 0;
931
932
0
  r = unlink(cache->cache.info.u.nscookiejar.filepath);
933
0
  if (r)
934
0
    lwsl_warn("%s: failed to unlink %s\n", __func__,
935
0
        cache->cache.info.u.nscookiejar.filepath);
936
937
0
  return r;
938
0
}
939
940
static void
941
lws_cache_nscookiejar_destroy(struct lws_cache_ttl_lru **_pc)
942
0
{
943
0
  lws_cache_nscookiejar_t *cache = (lws_cache_nscookiejar_t *)*_pc;
944
945
0
  if (!cache)
946
0
    return;
947
948
0
  lws_sul_cancel(&cache->cache.sul);
949
950
0
  lws_free_set_NULL(*_pc);
951
0
}
952
953
#if defined(_DEBUG)
954
955
static int
956
nsc_dump_cb(lws_cache_nscookiejar_t *cache, void *opaque, int flags,
957
        const char *buf, size_t size)
958
0
{
959
0
  lwsl_hexdump_cache(buf, size);
960
961
0
  return 0;
962
0
}
963
964
static void
965
lws_cache_nscookiejar_debug_dump(struct lws_cache_ttl_lru *_c)
966
0
{
967
0
  lws_cache_nscookiejar_t *cache = (lws_cache_nscookiejar_t *)_c;
968
0
  int fd = nsc_backing_open_lock(cache, LWS_O_RDONLY, __func__);
969
970
0
  if (fd < 0)
971
0
    return;
972
973
0
  lwsl_cache("%s: %s\n", __func__, _c->info.name);
974
975
0
  nscookiejar_iterate(cache, fd, nsc_dump_cb, NULL);
976
977
0
  nsc_backing_close_unlock(cache, fd);
978
0
}
979
#endif
980
981
const struct lws_cache_ops lws_cache_ops_nscookiejar = {
982
  .create     = lws_cache_nscookiejar_create,
983
  .destroy    = lws_cache_nscookiejar_destroy,
984
  .expunge    = lws_cache_nscookiejar_expunge,
985
986
  .write      = lws_cache_nscookiejar_write,
987
  .tag_match    = lws_cache_nscookiejar_tag_match,
988
  .lookup     = lws_cache_nscookiejar_lookup,
989
  .invalidate   = lws_cache_nscookiejar_invalidate,
990
  .get      = lws_cache_nscookiejar_get,
991
#if defined(_DEBUG)
992
  .debug_dump   = lws_cache_nscookiejar_debug_dump,
993
#endif
994
};