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