mirror of
https://libwebsockets.org/repo/libwebsockets
synced 2024-11-24 01:39:33 +00:00
961 lines
23 KiB
C
961 lines
23 KiB
C
/*
|
|
* libwebsockets - small server side websockets and web server implementation
|
|
*
|
|
* Copyright (C) 2010 - 2021 Andy Green <andy@warmcat.com>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to
|
|
* deal in the Software without restriction, including without limitation the
|
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
* sell copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
* IN THE SOFTWARE.
|
|
*
|
|
* Implements a cache backing store compatible with netscape cookies.txt format
|
|
* There is one entry per "line", and fields are tab-delimited
|
|
*
|
|
* We need to know the format here, because while the unique cookie tag consists
|
|
* of "hostname|urlpath|cookiename", that does not appear like that in the file;
|
|
* we have to go parse the fields and synthesize the corresponding tag.
|
|
*
|
|
* We rely on all the fields except the cookie value fitting in a 256 byte
|
|
* buffer, and allow eating multiple buffers to get a huge cookie values.
|
|
*
|
|
* Because the cookie file is a device-wide asset, although lws will change it
|
|
* from the lws thread without conflict, there may be other processes that will
|
|
* change it by removal and regenerating the file asynchronously. For that
|
|
* reason, file handles are opened fresh each time we want to use the file, so
|
|
* we always get the latest version.
|
|
*
|
|
* When updating the file ourselves, we use a lockfile to ensure our process
|
|
* has exclusive access.
|
|
*
|
|
*
|
|
* Tag Matching rules
|
|
*
|
|
* There are three kinds of tag matching rules
|
|
*
|
|
* 1) specific - tag strigs must be the same
|
|
* 2) wilcard - tags matched using optional wildcards
|
|
* 3) wildcard + lookup - wildcard, but path part matches using cookie scope rules
|
|
*
|
|
*/
|
|
|
|
#include <private-lib-core.h>
|
|
#include "private-lib-misc-cache-ttl.h"
|
|
|
|
typedef enum nsc_iterator_ret {
|
|
NIR_CONTINUE = 0,
|
|
NIR_FINISH_OK = 1,
|
|
NIR_FINISH_ERROR = -1
|
|
} nsc_iterator_ret_t;
|
|
|
|
typedef enum cbreason {
|
|
LCN_SOL = (1 << 0),
|
|
LCN_EOL = (1 << 1)
|
|
} cbreason_t;
|
|
|
|
typedef int (*nsc_cb_t)(lws_cache_nscookiejar_t *cache, void *opaque, int flags,
|
|
const char *buf, size_t size);
|
|
|
|
static void
|
|
expiry_cb(lws_sorted_usec_list_t *sul);
|
|
|
|
static int
|
|
nsc_backing_open_lock(lws_cache_nscookiejar_t *cache, int mode, const char *par)
|
|
{
|
|
int sanity = 50;
|
|
char lock[128];
|
|
int fd_lock, fd;
|
|
|
|
lwsl_debug("%s: %s\n", __func__, par);
|
|
|
|
lws_snprintf(lock, sizeof(lock), "%s.LCK",
|
|
cache->cache.info.u.nscookiejar.filepath);
|
|
|
|
do {
|
|
fd_lock = open(lock, LWS_O_CREAT | O_EXCL, 0600);
|
|
if (fd_lock >= 0) {
|
|
close(fd_lock);
|
|
break;
|
|
}
|
|
|
|
if (!sanity--) {
|
|
lwsl_warn("%s: unable to lock %s: errno %d\n", __func__,
|
|
lock, errno);
|
|
return -1;
|
|
}
|
|
|
|
#if defined(WIN32)
|
|
Sleep(100);
|
|
#else
|
|
usleep(100000);
|
|
#endif
|
|
} while (1);
|
|
|
|
fd = open(cache->cache.info.u.nscookiejar.filepath,
|
|
LWS_O_CREAT | mode, 0600);
|
|
|
|
if (fd == -1) {
|
|
lwsl_warn("%s: unable to open or create %s\n", __func__,
|
|
cache->cache.info.u.nscookiejar.filepath);
|
|
unlink(lock);
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
|
|
static void
|
|
nsc_backing_close_unlock(lws_cache_nscookiejar_t *cache, int fd)
|
|
{
|
|
char lock[128];
|
|
|
|
lwsl_debug("%s\n", __func__);
|
|
|
|
lws_snprintf(lock, sizeof(lock), "%s.LCK",
|
|
cache->cache.info.u.nscookiejar.filepath);
|
|
if (fd >= 0)
|
|
close(fd);
|
|
unlink(lock);
|
|
}
|
|
|
|
/*
|
|
* We're going to call the callback with chunks of the file with flags
|
|
* indicating we're giving it the start of a line and / or giving it the end
|
|
* of a line.
|
|
*
|
|
* It's like this because the cookie value may be huge (and to a lesser extent
|
|
* the path may also be big).
|
|
*
|
|
* If it's the start of a line (flags on the cb has LCN_SOL), then the buffer
|
|
* contains up to the first 256 chars of the line, it's enough to match with.
|
|
*
|
|
* We cannot hold the file open inbetweentimes, since other processes may
|
|
* regenerate it, so we need to bind to a new inode. We open it with an
|
|
* exclusive flock() so other processes can't replace conflicting changes
|
|
* while we also write changes, without having to wait and see our changes.
|
|
*/
|
|
|
|
static int
|
|
nscookiejar_iterate(lws_cache_nscookiejar_t *cache, int fd,
|
|
nsc_cb_t cb, void *opaque)
|
|
{
|
|
int m = 0, n = 0, e, r = LCN_SOL, ignore = 0, ret = 0;
|
|
char temp[256], eof = 0;
|
|
|
|
if (lseek(fd, 0, SEEK_SET) == (off_t)-1)
|
|
return -1;
|
|
|
|
do { /* for as many buffers in the file */
|
|
|
|
int n1;
|
|
|
|
lwsl_debug("%s: n %d, m %d\n", __func__, n, m);
|
|
|
|
read:
|
|
n1 = (int)read(fd, temp + n, sizeof(temp) - (size_t)n);
|
|
|
|
lwsl_debug("%s: n1 %d\n", __func__, n1);
|
|
|
|
if (n1 <= 0) {
|
|
eof = 1;
|
|
if (m == n)
|
|
continue;
|
|
} else
|
|
n += n1;
|
|
|
|
while (m < n) {
|
|
|
|
m++;
|
|
|
|
if (temp[m - 1] != '\n')
|
|
continue;
|
|
|
|
/* ie, we hit EOL */
|
|
|
|
if (temp[0] == '#')
|
|
/* lines starting with # are comments */
|
|
e = 0;
|
|
else
|
|
e = cb(cache, opaque, r | LCN_EOL, temp,
|
|
(size_t)m - 1);
|
|
r = LCN_SOL;
|
|
ignore = 0;
|
|
/*
|
|
* Move back remainder and prefill the gap that opened
|
|
* up: we want to pass enough in the start chunk so the
|
|
* cb can classify it even if it can't get all the
|
|
* value part in one go
|
|
*/
|
|
memmove(temp, temp + m, (size_t)(n - m));
|
|
n -= m;
|
|
m = 0;
|
|
|
|
if (e) {
|
|
ret = e;
|
|
goto bail;
|
|
}
|
|
|
|
goto read;
|
|
}
|
|
|
|
if (m) {
|
|
/* we ran out of buffer */
|
|
if (ignore || (r == LCN_SOL && n && temp[0] == '#')) {
|
|
e = 0;
|
|
ignore = 1;
|
|
} else {
|
|
e = cb(cache, opaque,
|
|
r | (n == m && eof ? LCN_EOL : 0),
|
|
temp, (size_t)m);
|
|
|
|
m = 0;
|
|
n = 0;
|
|
}
|
|
|
|
if (e) {
|
|
/*
|
|
* We have to call off the whole thing if any
|
|
* step, eg, OOMs
|
|
*/
|
|
ret = e;
|
|
goto bail;
|
|
}
|
|
r = 0;
|
|
}
|
|
|
|
} while (!eof || n != m);
|
|
|
|
ret = 0;
|
|
|
|
bail:
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* lookup() just handles wildcard resolution, it doesn't deal with moving the
|
|
* hits to L1. That has to be done individually by non-wildcard names.
|
|
*/
|
|
|
|
enum {
|
|
NSC_COL_HOST = 0, /* wc idx 0 */
|
|
NSC_COL_PATH = 2, /* wc idx 1 */
|
|
NSC_COL_EXPIRY = 4,
|
|
NSC_COL_NAME = 5, /* wc idx 2 */
|
|
|
|
NSC_COL_COUNT = 6
|
|
};
|
|
|
|
/*
|
|
* This performs the specialized wildcard that knows about cookie path match
|
|
* rules.
|
|
*
|
|
* To defeat the lookup path matching, lie to it about idx being NSC_COL_PATH
|
|
*/
|
|
|
|
static int
|
|
nsc_match(const char *wc, size_t wc_len, const char *col, size_t col_len,
|
|
int idx)
|
|
{
|
|
size_t n = 0;
|
|
|
|
if (idx != NSC_COL_PATH)
|
|
return lws_strcmp_wildcard(wc, wc_len, col, col_len);
|
|
|
|
/*
|
|
* Cookie path match is special, if we lookup on a path like /my/path,
|
|
* we must match on cookie paths for every dir level including /, so
|
|
* match on /, /my, and /my/path. But we must not match on /m or
|
|
* /my/pa etc. If we lookup on /, we must not match /my/path
|
|
*
|
|
* Let's go through wc checking at / and for every complete subpath if
|
|
* it is an explicit match
|
|
*/
|
|
|
|
if (!strcmp(col, wc))
|
|
return 0; /* exact hit */
|
|
|
|
while (n <= wc_len) {
|
|
if (n == wc_len || wc[n] == '/') {
|
|
if (n && col_len <= n && !strncmp(wc, col, n))
|
|
return 0; /* hit */
|
|
|
|
if (n != wc_len && col_len <= n + 1 &&
|
|
!strncmp(wc, col, n + 1)) /* check for trailing / */
|
|
return 0; /* hit */
|
|
}
|
|
n++;
|
|
}
|
|
|
|
return 1; /* fail */
|
|
}
|
|
|
|
static const uint8_t nsc_cols[] = { NSC_COL_HOST, NSC_COL_PATH, NSC_COL_NAME };
|
|
|
|
static int
|
|
lws_cache_nscookiejar_tag_match(struct lws_cache_ttl_lru *cache,
|
|
const char *wc, const char *tag, char lookup)
|
|
{
|
|
const char *wc_end = wc + strlen(wc), *tag_end = tag + strlen(tag),
|
|
*start_wc, *start_tag;
|
|
int n = 0;
|
|
|
|
lwsl_cache("%s: '%s' vs '%s'\n", __func__, wc, tag);
|
|
|
|
/*
|
|
* Given a well-formed host|path|name tag and a wildcard term,
|
|
* make the determination if the tag matches the wildcard or not,
|
|
* using lookup rules that apply at this cache level.
|
|
*/
|
|
|
|
while (n < 3) {
|
|
start_wc = wc;
|
|
while (wc < wc_end && *wc != LWSCTAG_SEP)
|
|
wc++;
|
|
|
|
start_tag = tag;
|
|
while (tag < tag_end && *tag != LWSCTAG_SEP)
|
|
tag++;
|
|
|
|
lwsl_cache("%s: '%.*s' vs '%.*s'\n", __func__,
|
|
lws_ptr_diff(wc, start_wc), start_wc,
|
|
lws_ptr_diff(tag, start_tag), start_tag);
|
|
if (nsc_match(start_wc, lws_ptr_diff_size_t(wc, start_wc),
|
|
start_tag, lws_ptr_diff_size_t(tag, start_tag),
|
|
lookup ? nsc_cols[n] : NSC_COL_HOST)) {
|
|
lwsl_cache("%s: fail\n", __func__);
|
|
return 1;
|
|
}
|
|
|
|
if (wc < wc_end)
|
|
wc++;
|
|
if (tag < tag_end)
|
|
tag++;
|
|
|
|
n++;
|
|
}
|
|
|
|
lwsl_cache("%s: hit\n", __func__);
|
|
|
|
return 0; /* match */
|
|
}
|
|
|
|
/*
|
|
* Converts the start of a cookie file line into a tag
|
|
*/
|
|
|
|
static int
|
|
nsc_line_to_tag(const char *buf, size_t size, char *tag, size_t max_tag,
|
|
lws_usec_t *pexpiry)
|
|
{
|
|
int n, idx = 0, tl = 0;
|
|
lws_usec_t expiry = 0;
|
|
size_t bn = 0;
|
|
char col[64];
|
|
|
|
if (size < 3)
|
|
return 1;
|
|
|
|
while (bn < size && idx <= NSC_COL_NAME) {
|
|
|
|
n = 0;
|
|
while (bn < size && n < (int)sizeof(col) - 1 &&
|
|
buf[bn] != '\t')
|
|
col[n++] = buf[bn++];
|
|
col[n] = '\0';
|
|
if (buf[bn] == '\t')
|
|
bn++;
|
|
|
|
switch (idx) {
|
|
case NSC_COL_EXPIRY:
|
|
expiry = (lws_usec_t)((unsigned long long)atoll(col) *
|
|
(lws_usec_t)LWS_US_PER_SEC);
|
|
break;
|
|
|
|
case NSC_COL_HOST:
|
|
case NSC_COL_PATH:
|
|
case NSC_COL_NAME:
|
|
|
|
/*
|
|
* As we match the pieces of the wildcard,
|
|
* compose the matches into a specific tag
|
|
*/
|
|
|
|
if (tl + n + 2 > (int)max_tag)
|
|
return 1;
|
|
if (tl)
|
|
tag[tl++] = LWSCTAG_SEP;
|
|
memcpy(tag + tl, col, (size_t)n);
|
|
tl += n;
|
|
tag[tl] = '\0';
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
idx++;
|
|
}
|
|
|
|
if (pexpiry)
|
|
*pexpiry = expiry;
|
|
|
|
lwsl_info("%s: %.*s: tag '%s'\n", __func__, (int)size, buf, tag);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct nsc_lookup_ctx {
|
|
const char *wildcard_key;
|
|
lws_dll2_owner_t *results_owner;
|
|
lws_cache_match_t *match; /* current match if any */
|
|
size_t wklen;
|
|
};
|
|
|
|
|
|
static int
|
|
nsc_lookup_cb(lws_cache_nscookiejar_t *cache, void *opaque, int flags,
|
|
const char *buf, size_t size)
|
|
{
|
|
struct nsc_lookup_ctx *ctx = (struct nsc_lookup_ctx *)opaque;
|
|
lws_usec_t expiry;
|
|
char tag[200];
|
|
int tl;
|
|
|
|
if (!(flags & LCN_SOL)) {
|
|
if (ctx->match)
|
|
ctx->match->payload_size += size;
|
|
|
|
return NIR_CONTINUE;
|
|
}
|
|
|
|
/*
|
|
* There should be enough in buf to match or reject it... let's
|
|
* synthesize a tag from the text "line" and then check the tags for
|
|
* a match
|
|
*/
|
|
|
|
ctx->match = NULL; /* new SOL means stop tracking payload len */
|
|
|
|
if (nsc_line_to_tag(buf, size, tag, sizeof(tag), &expiry))
|
|
return NIR_CONTINUE;
|
|
|
|
if (lws_cache_nscookiejar_tag_match(&cache->cache,
|
|
ctx->wildcard_key, tag, 1))
|
|
return NIR_CONTINUE;
|
|
|
|
tl = (int)strlen(tag);
|
|
|
|
/*
|
|
* ... it looks like a match then... create new match
|
|
* object with the specific tag, and add it to the owner list
|
|
*/
|
|
|
|
ctx->match = lws_fi(&cache->cache.info.cx->fic, "cache_lookup_oom") ? NULL :
|
|
lws_malloc(sizeof(*ctx->match) + (unsigned int)tl + 1u,
|
|
__func__);
|
|
if (!ctx->match)
|
|
/* caller of lookup will clean results list on fail */
|
|
return NIR_FINISH_ERROR;
|
|
|
|
ctx->match->payload_size = size;
|
|
ctx->match->tag_size = (size_t)tl;
|
|
ctx->match->expiry = expiry;
|
|
|
|
memset(&ctx->match->list, 0, sizeof(ctx->match->list));
|
|
memcpy(&ctx->match[1], tag, (size_t)tl + 1u);
|
|
lws_dll2_add_tail(&ctx->match->list, ctx->results_owner);
|
|
|
|
return NIR_CONTINUE;
|
|
}
|
|
|
|
static int
|
|
lws_cache_nscookiejar_lookup(struct lws_cache_ttl_lru *_c,
|
|
const char *wildcard_key,
|
|
lws_dll2_owner_t *results_owner)
|
|
{
|
|
lws_cache_nscookiejar_t *cache = (lws_cache_nscookiejar_t *)_c;
|
|
struct nsc_lookup_ctx ctx;
|
|
int ret, fd;
|
|
|
|
fd = nsc_backing_open_lock(cache, LWS_O_RDONLY, __func__);
|
|
if (fd < 0)
|
|
return 1;
|
|
|
|
ctx.wildcard_key = wildcard_key;
|
|
ctx.results_owner = results_owner;
|
|
ctx.wklen = strlen(wildcard_key);
|
|
ctx.match = 0;
|
|
|
|
ret = nscookiejar_iterate(cache, fd, nsc_lookup_cb, &ctx);
|
|
/*
|
|
* The cb can fail, eg, with OOM, making the whole lookup
|
|
* invalid and returning fail. Caller will clean
|
|
* results_owner on fail.
|
|
*/
|
|
nsc_backing_close_unlock(cache, fd);
|
|
|
|
return ret == NIR_FINISH_ERROR;
|
|
}
|
|
|
|
/*
|
|
* It's pretty horrible having to implement add or remove individual items by
|
|
* file regeneration, but if we don't want to keep it all in heap, and we want
|
|
* this cookie jar format, that is what we are into.
|
|
*
|
|
* Allow to optionally add a "line", optionally wildcard delete tags, and always
|
|
* delete expired entries.
|
|
*
|
|
* Although we can rely on the lws thread to be doing this, multiple processes
|
|
* may be using the cookie jar and can tread on each other. So we use flock()
|
|
* (linux only) to get exclusive access while we are processing this.
|
|
*
|
|
* We leave the existing file alone and generate a new one alongside it, with a
|
|
* fixed name.tmp format so it can't leak, if that went OK then we unlink the
|
|
* old and rename the new.
|
|
*/
|
|
|
|
struct nsc_regen_ctx {
|
|
const char *wildcard_key_delete;
|
|
const void *add_data;
|
|
lws_usec_t curr;
|
|
size_t add_size;
|
|
int fdt;
|
|
char drop;
|
|
};
|
|
|
|
/* only used by nsc_regen() */
|
|
|
|
static int
|
|
nsc_regen_cb(lws_cache_nscookiejar_t *cache, void *opaque, int flags,
|
|
const char *buf, size_t size)
|
|
{
|
|
struct nsc_regen_ctx *ctx = (struct nsc_regen_ctx *)opaque;
|
|
char tag[256];
|
|
lws_usec_t expiry;
|
|
|
|
if (flags & LCN_SOL) {
|
|
|
|
ctx->drop = 0;
|
|
|
|
if (nsc_line_to_tag(buf, size, tag, sizeof(tag), &expiry))
|
|
/* filter it out if it is unparseable */
|
|
goto drop;
|
|
|
|
/* routinely track the earliest expiry */
|
|
|
|
if (!cache->earliest_expiry ||
|
|
(expiry && cache->earliest_expiry > expiry))
|
|
cache->earliest_expiry = expiry;
|
|
|
|
if (expiry && expiry < ctx->curr)
|
|
/* routinely strip anything beyond its expiry */
|
|
goto drop;
|
|
|
|
if (ctx->wildcard_key_delete)
|
|
lwsl_cache("%s: %s vs %s\n", __func__,
|
|
tag, ctx->wildcard_key_delete);
|
|
if (ctx->wildcard_key_delete &&
|
|
!lws_cache_nscookiejar_tag_match(&cache->cache,
|
|
ctx->wildcard_key_delete,
|
|
tag, 0)) {
|
|
lwsl_cache("%s: %s matches wc delete %s\n", __func__,
|
|
tag, ctx->wildcard_key_delete);
|
|
goto drop;
|
|
}
|
|
}
|
|
|
|
if (ctx->drop)
|
|
return 0;
|
|
|
|
cache->cache.current_footprint += (uint64_t)size;
|
|
|
|
if (write(ctx->fdt, buf, /*msvc*/(unsigned int)size) != (ssize_t)size)
|
|
return NIR_FINISH_ERROR;
|
|
|
|
if (flags & LCN_EOL)
|
|
if ((size_t)write(ctx->fdt, "\n", 1) != 1)
|
|
return NIR_FINISH_ERROR;
|
|
|
|
return 0;
|
|
|
|
drop:
|
|
ctx->drop = 1;
|
|
|
|
return NIR_CONTINUE;
|
|
}
|
|
|
|
static int
|
|
nsc_regen(lws_cache_nscookiejar_t *cache, const char *wc_delete,
|
|
const void *pay, size_t pay_size)
|
|
{
|
|
struct nsc_regen_ctx ctx;
|
|
char filepath[128];
|
|
int fd, ret = 1;
|
|
|
|
fd = nsc_backing_open_lock(cache, LWS_O_RDONLY, __func__);
|
|
if (fd < 0)
|
|
return 1;
|
|
|
|
lws_snprintf(filepath, sizeof(filepath), "%s.tmp",
|
|
cache->cache.info.u.nscookiejar.filepath);
|
|
unlink(filepath);
|
|
|
|
if (lws_fi(&cache->cache.info.cx->fic, "cache_regen_temp_open"))
|
|
goto bail;
|
|
|
|
ctx.fdt = open(filepath, LWS_O_CREAT | LWS_O_WRONLY, 0600);
|
|
if (ctx.fdt < 0)
|
|
goto bail;
|
|
|
|
/* magic header */
|
|
|
|
if (lws_fi(&cache->cache.info.cx->fic, "cache_regen_temp_write") ||
|
|
/* other consumers insist to see this at start of cookie jar */
|
|
write(ctx.fdt, "# Netscape HTTP Cookie File\n", 28) != 28)
|
|
goto bail1;
|
|
|
|
/* if we are adding something, put it first */
|
|
|
|
if (pay &&
|
|
write(ctx.fdt, pay, /*msvc*/(unsigned int)pay_size) !=
|
|
(ssize_t)pay_size)
|
|
goto bail1;
|
|
if (pay && write(ctx.fdt, "\n", 1u) != (ssize_t)1)
|
|
goto bail1;
|
|
|
|
cache->cache.current_footprint = 0;
|
|
|
|
ctx.wildcard_key_delete = wc_delete;
|
|
ctx.add_data = pay;
|
|
ctx.add_size = pay_size;
|
|
ctx.curr = lws_now_usecs();
|
|
ctx.drop = 0;
|
|
|
|
cache->earliest_expiry = 0;
|
|
|
|
if (lws_fi(&cache->cache.info.cx->fic, "cache_regen_iter_fail") ||
|
|
nscookiejar_iterate(cache, fd, nsc_regen_cb, &ctx))
|
|
goto bail1;
|
|
|
|
close(ctx.fdt);
|
|
ctx.fdt = -1;
|
|
|
|
if (unlink(cache->cache.info.u.nscookiejar.filepath) == -1)
|
|
lwsl_info("%s: unlink %s failed\n", __func__,
|
|
cache->cache.info.u.nscookiejar.filepath);
|
|
if (rename(filepath, cache->cache.info.u.nscookiejar.filepath) == -1)
|
|
lwsl_info("%s: rename %s failed\n", __func__,
|
|
cache->cache.info.u.nscookiejar.filepath);
|
|
|
|
if (cache->earliest_expiry)
|
|
lws_cache_schedule(&cache->cache, expiry_cb,
|
|
cache->earliest_expiry);
|
|
|
|
ret = 0;
|
|
goto bail;
|
|
|
|
bail1:
|
|
if (ctx.fdt >= 0)
|
|
close(ctx.fdt);
|
|
bail:
|
|
unlink(filepath);
|
|
|
|
nsc_backing_close_unlock(cache, fd);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
expiry_cb(lws_sorted_usec_list_t *sul)
|
|
{
|
|
lws_cache_nscookiejar_t *cache = lws_container_of(sul,
|
|
lws_cache_nscookiejar_t, cache.sul);
|
|
|
|
/*
|
|
* regen the cookie jar without changes, so expired are removed and
|
|
* new earliest expired computed
|
|
*/
|
|
if (nsc_regen(cache, NULL, NULL, 0))
|
|
return;
|
|
|
|
if (cache->earliest_expiry)
|
|
lws_cache_schedule(&cache->cache, expiry_cb,
|
|
cache->earliest_expiry);
|
|
}
|
|
|
|
|
|
/* specific_key and expiry are ignored, since it must be encoded in payload */
|
|
|
|
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)
|
|
{
|
|
lws_cache_nscookiejar_t *cache = (lws_cache_nscookiejar_t *)_c;
|
|
char tag[128];
|
|
|
|
lwsl_cache("%s: %s: len %d\n", __func__, _c->info.name, (int)size);
|
|
|
|
assert(source);
|
|
|
|
if (nsc_line_to_tag((const char *)source, size, tag, sizeof(tag), NULL))
|
|
return 1;
|
|
|
|
if (ppvoid)
|
|
*ppvoid = NULL;
|
|
|
|
if (nsc_regen(cache, tag, source, size)) {
|
|
lwsl_err("%s: regen failed\n", __func__);
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct nsc_get_ctx {
|
|
struct lws_buflist *buflist;
|
|
const char *specific_key;
|
|
const void **pdata;
|
|
size_t *psize;
|
|
lws_cache_ttl_lru_t *l1;
|
|
lws_usec_t expiry;
|
|
};
|
|
|
|
/*
|
|
* We're looking for a specific key, if found, we want to make an entry for it
|
|
* in L1 and return information about that
|
|
*/
|
|
|
|
static int
|
|
nsc_get_cb(lws_cache_nscookiejar_t *cache, void *opaque, int flags,
|
|
const char *buf, size_t size)
|
|
{
|
|
struct nsc_get_ctx *ctx = (struct nsc_get_ctx *)opaque;
|
|
char tag[200];
|
|
uint8_t *q;
|
|
|
|
if (ctx->buflist)
|
|
goto collect;
|
|
|
|
if (!(flags & LCN_SOL))
|
|
return NIR_CONTINUE;
|
|
|
|
if (nsc_line_to_tag(buf, size, tag, sizeof(tag), &ctx->expiry)) {
|
|
lwsl_err("%s: can't get tag\n", __func__);
|
|
return NIR_CONTINUE;
|
|
}
|
|
|
|
lwsl_cache("%s: %s %s\n", __func__, ctx->specific_key, tag);
|
|
|
|
if (strcmp(ctx->specific_key, tag)) {
|
|
lwsl_cache("%s: no match\n", __func__);
|
|
return NIR_CONTINUE;
|
|
}
|
|
|
|
/* it's a match */
|
|
|
|
lwsl_cache("%s: IS match\n", __func__);
|
|
|
|
if (!(flags & LCN_EOL))
|
|
goto collect;
|
|
|
|
/* it all fit in the buffer, let's create it in L1 now */
|
|
|
|
*ctx->psize = size;
|
|
if (ctx->l1->info.ops->write(ctx->l1,
|
|
ctx->specific_key, (const uint8_t *)buf,
|
|
size, ctx->expiry, (void **)ctx->pdata))
|
|
return NIR_FINISH_ERROR;
|
|
|
|
return NIR_FINISH_OK;
|
|
|
|
collect:
|
|
/*
|
|
* it's bigger than one buffer-load, we have to stash what we're getting
|
|
* on a buflist and create it when we have it all
|
|
*/
|
|
|
|
if (lws_buflist_append_segment(&ctx->buflist, (const uint8_t *)buf,
|
|
size))
|
|
goto cleanup;
|
|
|
|
if (!(flags & LCN_EOL))
|
|
return NIR_CONTINUE;
|
|
|
|
/* we have all the payload, create the L1 entry without payload yet */
|
|
|
|
*ctx->psize = size;
|
|
if (ctx->l1->info.ops->write(ctx->l1, ctx->specific_key, NULL,
|
|
lws_buflist_total_len(&ctx->buflist),
|
|
ctx->expiry, (void **)&q))
|
|
goto cleanup;
|
|
*ctx->pdata = q;
|
|
|
|
/* dump the buflist into the L1 cache entry */
|
|
|
|
do {
|
|
uint8_t *p;
|
|
size_t len = lws_buflist_next_segment_len(&ctx->buflist, &p);
|
|
|
|
memcpy(q, p, len);
|
|
q += len;
|
|
|
|
lws_buflist_use_segment(&ctx->buflist, len);
|
|
} while (ctx->buflist);
|
|
|
|
return NIR_FINISH_OK;
|
|
|
|
cleanup:
|
|
lws_buflist_destroy_all_segments(&ctx->buflist);
|
|
|
|
return NIR_FINISH_ERROR;
|
|
}
|
|
|
|
static int
|
|
lws_cache_nscookiejar_get(struct lws_cache_ttl_lru *_c,
|
|
const char *specific_key, const void **pdata,
|
|
size_t *psize)
|
|
{
|
|
lws_cache_nscookiejar_t *cache = (lws_cache_nscookiejar_t *)_c;
|
|
struct nsc_get_ctx ctx;
|
|
int ret, fd;
|
|
|
|
fd = nsc_backing_open_lock(cache, LWS_O_RDONLY, __func__);
|
|
if (fd < 0)
|
|
return 1;
|
|
|
|
/* get a pointer to l1 */
|
|
ctx.l1 = &cache->cache;
|
|
while (ctx.l1->child)
|
|
ctx.l1 = ctx.l1->child;
|
|
|
|
ctx.pdata = pdata;
|
|
ctx.psize = psize;
|
|
ctx.specific_key = specific_key;
|
|
ctx.buflist = NULL;
|
|
ctx.expiry = 0;
|
|
|
|
ret = nscookiejar_iterate(cache, fd, nsc_get_cb, &ctx);
|
|
|
|
nsc_backing_close_unlock(cache, fd);
|
|
|
|
return ret != NIR_FINISH_OK;
|
|
}
|
|
|
|
static int
|
|
lws_cache_nscookiejar_invalidate(struct lws_cache_ttl_lru *_c,
|
|
const char *wc_key)
|
|
{
|
|
lws_cache_nscookiejar_t *cache = (lws_cache_nscookiejar_t *)_c;
|
|
|
|
return nsc_regen(cache, wc_key, NULL, 0);
|
|
}
|
|
|
|
static struct lws_cache_ttl_lru *
|
|
lws_cache_nscookiejar_create(const struct lws_cache_creation_info *info)
|
|
{
|
|
lws_cache_nscookiejar_t *cache;
|
|
|
|
cache = lws_fi(&info->cx->fic, "cache_createfail") ? NULL :
|
|
lws_zalloc(sizeof(*cache), __func__);
|
|
if (!cache)
|
|
return NULL;
|
|
|
|
cache->cache.info = *info;
|
|
|
|
/*
|
|
* We need to scan the file, if it exists, and find the earliest
|
|
* expiry while cleaning out any expired entries
|
|
*/
|
|
expiry_cb(&cache->cache.sul);
|
|
|
|
lwsl_notice("%s: create %s\n", __func__, info->name ? info->name : "?");
|
|
|
|
return (struct lws_cache_ttl_lru *)cache;
|
|
}
|
|
|
|
static int
|
|
lws_cache_nscookiejar_expunge(struct lws_cache_ttl_lru *_c)
|
|
{
|
|
lws_cache_nscookiejar_t *cache = (lws_cache_nscookiejar_t *)_c;
|
|
int r;
|
|
|
|
if (!cache)
|
|
return 0;
|
|
|
|
r = unlink(cache->cache.info.u.nscookiejar.filepath);
|
|
if (r)
|
|
lwsl_warn("%s: failed to unlink %s\n", __func__,
|
|
cache->cache.info.u.nscookiejar.filepath);
|
|
|
|
return r;
|
|
}
|
|
|
|
static void
|
|
lws_cache_nscookiejar_destroy(struct lws_cache_ttl_lru **_pc)
|
|
{
|
|
lws_cache_nscookiejar_t *cache = (lws_cache_nscookiejar_t *)*_pc;
|
|
|
|
if (!cache)
|
|
return;
|
|
|
|
lws_sul_cancel(&cache->cache.sul);
|
|
|
|
lws_free_set_NULL(*_pc);
|
|
}
|
|
|
|
#if defined(_DEBUG)
|
|
|
|
static int
|
|
nsc_dump_cb(lws_cache_nscookiejar_t *cache, void *opaque, int flags,
|
|
const char *buf, size_t size)
|
|
{
|
|
lwsl_hexdump_cache(buf, size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
lws_cache_nscookiejar_debug_dump(struct lws_cache_ttl_lru *_c)
|
|
{
|
|
lws_cache_nscookiejar_t *cache = (lws_cache_nscookiejar_t *)_c;
|
|
int fd = nsc_backing_open_lock(cache, LWS_O_RDONLY, __func__);
|
|
|
|
if (fd < 0)
|
|
return;
|
|
|
|
lwsl_cache("%s: %s\n", __func__, _c->info.name);
|
|
|
|
nscookiejar_iterate(cache, fd, nsc_dump_cb, NULL);
|
|
|
|
nsc_backing_close_unlock(cache, fd);
|
|
}
|
|
#endif
|
|
|
|
const struct lws_cache_ops lws_cache_ops_nscookiejar = {
|
|
.create = lws_cache_nscookiejar_create,
|
|
.destroy = lws_cache_nscookiejar_destroy,
|
|
.expunge = lws_cache_nscookiejar_expunge,
|
|
|
|
.write = lws_cache_nscookiejar_write,
|
|
.tag_match = lws_cache_nscookiejar_tag_match,
|
|
.lookup = lws_cache_nscookiejar_lookup,
|
|
.invalidate = lws_cache_nscookiejar_invalidate,
|
|
.get = lws_cache_nscookiejar_get,
|
|
#if defined(_DEBUG)
|
|
.debug_dump = lws_cache_nscookiejar_debug_dump,
|
|
#endif
|
|
};
|