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