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