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 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
241 bail:
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
251 enum {
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
267 static int
nsc_match(const char * wc,size_t wc_len,const char * col,size_t col_len,int idx)268 nsc_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
304 static const uint8_t nsc_cols[] = { NSC_COL_HOST, NSC_COL_PATH, NSC_COL_NAME };
305
306 static int
lws_cache_nscookiejar_tag_match(struct lws_cache_ttl_lru * cache,const char * wc,const char * tag,char lookup)307 lws_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
358 static int
nsc_line_to_tag(const char * buf,size_t size,char * tag,size_t max_tag,lws_usec_t * pexpiry)359 nsc_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
418 struct 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
426 static int
nsc_lookup_cb(lws_cache_nscookiejar_t * cache,void * opaque,int flags,const char * buf,size_t size)427 nsc_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
482 static int
lws_cache_nscookiejar_lookup(struct lws_cache_ttl_lru * _c,const char * wildcard_key,lws_dll2_owner_t * results_owner)483 lws_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
528 struct 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
539 static int
nsc_regen_cb(lws_cache_nscookiejar_t * cache,void * opaque,int flags,const char * buf,size_t size)540 nsc_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
592 drop:
593 ctx->drop = 1;
594
595 return NIR_CONTINUE;
596 }
597
598 static int
nsc_regen(lws_cache_nscookiejar_t * cache,const char * wc_delete,const void * pay,size_t pay_size)599 nsc_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
668 bail1:
669 if (ctx.fdt >= 0)
670 close(ctx.fdt);
671 bail:
672 unlink(filepath);
673
674 nsc_backing_close_unlock(cache, fd);
675
676 return ret;
677 }
678
679 static void
expiry_cb(lws_sorted_usec_list_t * sul)680 expiry_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
700 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)701 lws_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
727 struct 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
741 static int
nsc_get_cb(lws_cache_nscookiejar_t * cache,void * opaque,int flags,const char * buf,size_t size)742 nsc_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
784 collect:
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
820 cleanup:
821 lws_buflist_destroy_all_segments(&ctx->buflist);
822
823 return NIR_FINISH_ERROR;
824 }
825
826 static int
lws_cache_nscookiejar_get(struct lws_cache_ttl_lru * _c,const char * specific_key,const void ** pdata,size_t * psize)827 lws_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
857 static int
lws_cache_nscookiejar_invalidate(struct lws_cache_ttl_lru * _c,const char * wc_key)858 lws_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
866 static struct lws_cache_ttl_lru *
lws_cache_nscookiejar_create(const struct lws_cache_creation_info * info)867 lws_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
889 static int
lws_cache_nscookiejar_expunge(struct lws_cache_ttl_lru * _c)890 lws_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
906 static void
lws_cache_nscookiejar_destroy(struct lws_cache_ttl_lru ** _pc)907 lws_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
921 static int
nsc_dump_cb(lws_cache_nscookiejar_t * cache,void * opaque,int flags,const char * buf,size_t size)922 nsc_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
930 static void
lws_cache_nscookiejar_debug_dump(struct lws_cache_ttl_lru * _c)931 lws_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
947 const 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