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