1
0
Fork 0
mirror of https://github.com/warmcat/libwebsockets.git synced 2025-03-09 00:00:04 +01:00

http: cookies: support cookie jar in and out

This commit is contained in:
Yichen Gu 2021-07-05 16:41:41 +08:00 committed by Andy Green
parent b67d192100
commit b31c5d6ffe
24 changed files with 951 additions and 32 deletions

View file

@ -222,7 +222,7 @@
"platforms": "not linux-centos-8/x86_64-amd/gcc"
},
"lwsws2": {
"cmake": "-DLWS_WITH_LWSWS=ON -DLWS_WITHOUT_EXTENSIONS=0 -DLWS_WITH_HTTP2=1 -DLWS_WITH_ACME=1 -DLWS_WITH_MINIMAL_EXAMPLES=1 -DCMAKE_BUILD_TYPE=DEBUG -DLWS_WITH_LWS_DSH=1",
"cmake": "-DLWS_WITH_LWSWS=ON -DLWS_WITHOUT_EXTENSIONS=0 -DLWS_WITH_HTTP2=1 -DLWS_WITH_ACME=1 -DLWS_WITH_MINIMAL_EXAMPLES=1 -DCMAKE_BUILD_TYPE=DEBUG -DLWS_WITH_LWS_DSH=1 -DLWS_WITH_CACHE_NSCOOKIEJAR=0",
# no distro -devel package for libuv
"platforms": "not linux-centos-8/x86_64-amd/gcc"
},

View file

@ -278,7 +278,11 @@ option(LWS_WITH_SUL_DEBUGGING "Enable zombie lws_sul checking on object deletion
option(LWS_WITH_PLUGINS_API "Build generic lws_plugins apis (see LWS_WITH_PLUGINS to also build protocol plugins)" OFF)
option(LWS_WITH_CONMON "Collect introspectable connection latency stats on individual client connections" ON)
option(LWS_WITHOUT_EVENTFD "Force using pipe instead of eventfd" OFF)
option(LWS_WITH_CACHE_NSCOOKIEJAR "Build file-backed lws-cache-ttl that uses netscape cookie jar format (linux-only)" OFF)
if (UNIX OR WIN32)
option(LWS_WITH_CACHE_NSCOOKIEJAR "Build file-backed lws-cache-ttl that uses netscape cookie jar format (linux-only)" ON)
else()
option(LWS_WITH_CACHE_NSCOOKIEJAR "Build file-backed lws-cache-ttl that uses netscape cookie jar format (linux-only)" OFF)
endif()
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
option(LWS_WITH_NETLINK "Monitor Netlink for Routing Table changes" ON)

View file

@ -0,0 +1,40 @@
# Client http cookie storage, caching and application
lws now has the option to store incoming cookies in a Netscape cookie jar file
persistently, and auto-apply relevant cookies to future outgoing requests.
A L1 heap cache of recent cookies is maintained, along with LRU tracking and
removal of entries from cache and the cookie jar file according to their cookie
expiry time.
The cookie handling is off by default per-connection for backwards compatibility
and to avoid unexpected tracking.
## Enabling at build-time
Make sure `-DLWS_WITH_CACHE_NSCOOKIEJAR=1` is enabled at cmake (it is on by
default now).
## Configuring the cookie cache
The cookie cache is managed through context creation info struct members.
|member|function|
|---|---|
|`.http_nsc_filepath`|Filepath to store the cookie jar file at|
|`.http_nsc_heap_max_footprint`|0, or Max size in bytes for the L1 heap cache|
|`.http_nsc_heap_max_items`|0, or Max number of cookies allowed in L1 heap cache|
|`.http_nsc_heap_max_payload`|0, or Largest cookie we are willing to handle|
## Enabling per-connection in lws
To enable it on connections at lws level, add the flag `LCCSCF_CACHE_COOKIES` to
the client connection info struct `.ssl_connection` flags.
## Enabling per-connection in Secure Streams policy
To enable it on Secure Streams, in the streamtype policy add
```
"http_cookies": true
```

View file

@ -96,6 +96,9 @@ enum lws_client_connect_ssl_connection_flags {
LCCSCF_ACCEPT_TLS_DOWNGRADE_REDIRECTS = (1 << 29),
/**< By default lws rejects https redirecting to http. Set this
* flag on the client connection to allow it. */
LCCSCF_CACHE_COOKIES = (1 << 30),
/**< If built with -DLWS_WITH_CACHE_NSCOOKIEJAR, store and reapply
* http cookies in a Netscape Cookie Jar on this connection */
};
/** struct lws_client_connect_info - parameters to connect with when using

View file

@ -899,6 +899,21 @@ struct lws_context_creation_info {
/**< CONTEXT: NULL to use the default, process-scope logging context,
* else a specific logging context to associate with this context */
#if defined(LWS_WITH_CACHE_NSCOOKIEJAR) && defined(LWS_WITH_CLIENT)
const char *http_nsc_filepath;
/**< CONTEXT: Filepath to use for http netscape cookiejar file */
size_t http_nsc_heap_max_footprint;
/**< CONTEXT: 0, or limit in bytes for heap usage of memory cookie
* cache */
size_t http_nsc_heap_max_items;
/**< CONTEXT: 0, or the max number of items allowed in the cookie cache
* before destroying lru items to keep it under the limit */
size_t http_nsc_heap_max_payload;
/**< CONTEXT: 0, or the maximum size of a single cookie we are able to
* handle */
#endif
/* Add new things just above here ---^
* This is part of the ABI, don't needlessly break compatibility
*

View file

@ -160,6 +160,8 @@ enum {
/**< capture and report performace information */
LWSSSPOLF_DIRECT_PROTO_STR = (1 << 23),
/**< metadata as direct protocol string, e.g. http header */
LWSSSPOLF_HTTP_CACHE_COOKIES = (1 << 24),
/**< Record http cookies and pass them back on future requests */
};

View file

@ -386,6 +386,9 @@ lws_create_context(const struct lws_context_creation_info *info)
unsigned short count_threads = 1;
uint8_t *u;
uint16_t us_wait_resolution = 0;
#if defined(LWS_WITH_CACHE_NSCOOKIEJAR) && defined(LWS_WITH_CLIENT)
struct lws_cache_creation_info ci;
#endif
#if defined(__ANDROID__)
struct rlimit rt;
@ -412,6 +415,7 @@ lws_create_context(const struct lws_context_creation_info *info)
char fatal_exit_defer = 0;
#endif
if (lws_fi(&info->fic, "ctx_createfail1"))
goto early_bail;
@ -1374,6 +1378,34 @@ lws_create_context(const struct lws_context_creation_info *info)
}
}
#if defined(LWS_WITH_CACHE_NSCOOKIEJAR) && defined(LWS_WITH_CLIENT)
if (info->http_nsc_filepath) {
memset(&ci, 0, sizeof(ci));
ci.cx = context;
ci.ops = &lws_cache_ops_nscookiejar;
ci.name = "NSC";
ci.u.nscookiejar.filepath = info->http_nsc_filepath;
context->nsc = lws_cache_create(&ci);
if (!context->nsc)
goto bail;
ci.ops = &lws_cache_ops_heap;
ci.name = "L1";
ci.parent = context->nsc;
ci.max_footprint = info->http_nsc_heap_max_footprint;
ci.max_items = info->http_nsc_heap_max_items;
ci.max_payload = info->http_nsc_heap_max_payload;
context->l1 = lws_cache_create(&ci);
if (!context->l1) {
lwsl_err("Failed to init cookiejar");
goto bail;
}
}
#endif
#if defined(LWS_WITH_SECURE_STREAMS)
#if !defined(LWS_WITH_SECURE_STREAMS_STATIC_POLICY_ONLY)
@ -2080,6 +2112,11 @@ next:
lws_tls_jit_trust_inflight_destroy_all(context);
#endif
#if defined(LWS_WITH_CACHE_NSCOOKIEJAR) && defined(LWS_WITH_CLIENT)
lws_cache_destroy(&context->nsc);
lws_cache_destroy(&context->l1);
#endif
#if defined(LWS_WITH_SYS_SMD)
_lws_smd_destroy(context);
#endif

View file

@ -539,6 +539,9 @@ struct lws_context {
/**< Toplevel Fault Injection ctx */
#endif
#if defined(LWS_WITH_CACHE_NSCOOKIEJAR) && defined(LWS_WITH_CLIENT)
struct lws_cache_ttl_lru *l1, *nsc;
#endif
#if defined(LWS_WITH_SYS_NTPCLIENT)
void *ntpclient_priv;
@ -762,6 +765,13 @@ lws_jws_base64_enc(const char *in, size_t in_len, char *out, size_t out_max);
void
lws_vhost_destroy1(struct lws_vhost *vh);
#if defined(LWS_WITH_CACHE_NSCOOKIEJAR) && defined(LWS_WITH_CLIENT)
int
lws_parse_set_cookie(struct lws *wsi);
int
lws_cookie_send_cookies(struct lws *wsi, char **pp, char *end);
#endif
#if defined(LWS_PLAT_FREERTOS)
int
@ -872,6 +882,9 @@ lws_vhost_protocol_options(struct lws_vhost *vh, const char *name);
const struct lws_http_mount *
lws_find_mount(struct lws *wsi, const char *uri_ptr, int uri_len);
#ifdef LWS_WITH_HTTP2
int lws_wsi_is_h2(struct lws *wsi);
#endif
/*
* custom allocator
*/

View file

@ -85,7 +85,7 @@ nsc_backing_open_lock(lws_cache_nscookiejar_t *cache, int mode, const char *par)
do {
fd_lock = open(lock, LWS_O_CREAT | O_EXCL, 0600);
if (fd_lock != LWS_INVALID_FILE) {
if (fd_lock >= 0) {
close(fd_lock);
break;
}
@ -93,16 +93,20 @@ nsc_backing_open_lock(lws_cache_nscookiejar_t *cache, int mode, const char *par)
if (!sanity--) {
lwsl_warn("%s: unable to lock %s: errno %d\n", __func__,
lock, errno);
return LWS_INVALID_FILE;
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 == LWS_INVALID_FILE) {
if (fd == -1) {
lwsl_warn("%s: unable to open or create %s\n", __func__,
cache->cache.info.u.nscookiejar.filepath);
unlink(lock);
@ -120,7 +124,8 @@ nsc_backing_close_unlock(lws_cache_nscookiejar_t *cache, int fd)
lws_snprintf(lock, sizeof(lock), "%s.LCK",
cache->cache.info.u.nscookiejar.filepath);
close(fd);
if (fd >= 0)
close(fd);
unlink(lock);
}
@ -148,7 +153,8 @@ nscookiejar_iterate(lws_cache_nscookiejar_t *cache, int fd,
int m = 0, n = 0, e, r = LCN_SOL, ignore = 0, ret = 0;
char temp[256], eof = 0;
lseek(fd, 0, SEEK_SET);
if (lseek(fd, 0, SEEK_SET) == (off_t)-1)
return -1;
do { /* for as many buffers in the file */
@ -156,7 +162,8 @@ nscookiejar_iterate(lws_cache_nscookiejar_t *cache, int fd,
lwsl_debug("%s: n %d, m %d\n", __func__, n, m);
n1 = (int)read(fd, temp + m, sizeof(temp) - (size_t)m);
read:
n1 = (int)read(fd, temp + n, sizeof(temp) - (size_t)n);
lwsl_debug("%s: n1 %d\n", __func__, n1);
@ -198,6 +205,8 @@ nscookiejar_iterate(lws_cache_nscookiejar_t *cache, int fd,
ret = e;
goto bail;
}
goto read;
}
if (m) {
@ -480,12 +489,13 @@ lws_cache_nscookiejar_lookup(struct lws_cache_ttl_lru *_c,
int ret, fd;
fd = nsc_backing_open_lock(cache, LWS_O_RDONLY, __func__);
if (fd == LWS_INVALID_FILE)
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);
/*
@ -548,7 +558,7 @@ nsc_regen_cb(lws_cache_nscookiejar_t *cache, void *opaque, int flags,
(expiry && cache->earliest_expiry > expiry))
cache->earliest_expiry = expiry;
if (expiry < ctx->curr)
if (expiry && expiry < ctx->curr)
/* routinely strip anything beyond its expiry */
goto drop;
@ -570,7 +580,7 @@ nsc_regen_cb(lws_cache_nscookiejar_t *cache, void *opaque, int flags,
cache->cache.current_footprint += (uint64_t)size;
if ((size_t)write(ctx->fdt, buf, size) != size)
if (write(ctx->fdt, buf, /*msvc*/(unsigned int)size) != (ssize_t)size)
return NIR_FINISH_ERROR;
if (flags & LCN_EOL)
@ -594,7 +604,7 @@ nsc_regen(lws_cache_nscookiejar_t *cache, const char *wc_delete,
int fd, ret = 1;
fd = nsc_backing_open_lock(cache, LWS_O_RDONLY, __func__);
if (fd == LWS_INVALID_FILE)
if (fd < 0)
return 1;
lws_snprintf(filepath, sizeof(filepath), "%s.tmp",
@ -605,7 +615,7 @@ nsc_regen(lws_cache_nscookiejar_t *cache, const char *wc_delete,
goto bail;
ctx.fdt = open(filepath, LWS_O_CREAT | LWS_O_WRONLY, 0600);
if (ctx.fdt == LWS_INVALID_FILE)
if (ctx.fdt < 0)
goto bail;
/* magic header */
@ -617,9 +627,11 @@ nsc_regen(lws_cache_nscookiejar_t *cache, const char *wc_delete,
/* if we are adding something, put it first */
if (pay && (size_t)write(ctx.fdt, pay, pay_size) != pay_size)
if (pay &&
write(ctx.fdt, pay, /*msvc*/(unsigned int)pay_size) !=
(ssize_t)pay_size)
goto bail1;
if (pay && (size_t)write(ctx.fdt, "\n", 1) != 1)
if (pay && write(ctx.fdt, "\n", 1u) != (ssize_t)1)
goto bail1;
cache->cache.current_footprint = 0;
@ -637,19 +649,25 @@ nsc_regen(lws_cache_nscookiejar_t *cache, const char *wc_delete,
goto bail1;
close(ctx.fdt);
ctx.fdt = -1;
unlink(cache->cache.info.u.nscookiejar.filepath);
rename(filepath, cache->cache.info.u.nscookiejar.filepath);
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 bail1;
goto bail;
bail1:
close(ctx.fdt);
if (ctx.fdt >= 0)
close(ctx.fdt);
bail:
unlink(filepath);
@ -815,7 +833,7 @@ lws_cache_nscookiejar_get(struct lws_cache_ttl_lru *_c,
int ret, fd;
fd = nsc_backing_open_lock(cache, LWS_O_RDONLY, __func__);
if (fd == LWS_INVALID_FILE)
if (fd < 0)
return 1;
/* get a pointer to l1 */
@ -915,7 +933,7 @@ 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 == LWS_INVALID_FILE)
if (fd < 0)
return;
lwsl_cache("%s: %s\n", __func__, _c->info.name);

View file

@ -356,8 +356,7 @@ lws_cache_heap_write(struct lws_cache_ttl_lru *_c, const char *specific_key,
* matching rules at the backing store level
*/
if (!backing->info.ops->tag_match(backing, iname + 1,
specific_key, 1))
if (!strcmp(iname + 1, specific_key))
_lws_cache_heap_item_destroy(cache, i);
}

View file

@ -1398,11 +1398,15 @@ int lws_add_http2_header_by_name(struct lws *wsi, const unsigned char *name,
#if defined(_DEBUG)
/* value does not have to be NUL-terminated... %.*s not available on
* all platforms */
lws_strnncpy((char *)*p, (const char *)value, length,
lws_ptr_diff(end, (*p)));
if (value) {
lws_strnncpy((char *)*p, (const char *)value, length,
lws_ptr_diff(end, (*p)));
lwsl_header("%s: %p %s:%s (len %d)\n", __func__, *p, name,
(const char *)*p, length);
lwsl_header("%s: %p %s:%s (len %d)\n", __func__, *p, name,
(const char *)*p, length);
} else {
lwsl_err("%s: %p dummy copy %s (len %d)\n", __func__, *p, name, length);
}
#endif
len = (int)strlen((char *)name);
@ -1436,7 +1440,8 @@ int lws_add_http2_header_by_name(struct lws *wsi, const unsigned char *name,
if (lws_h2_num(7, (unsigned long)length, p, end))
return 1;
memcpy(*p, value, (unsigned int)length);
if (value)
memcpy(*p, value, (unsigned int)length);
*p += length;
return 0;

View file

@ -2625,6 +2625,11 @@ lws_h2_client_handshake(struct lws *wsi)
/* give userland a chance to append, eg, cookies */
#if defined(LWS_WITH_CACHE_NSCOOKIEJAR) && defined(LWS_WITH_CLIENT)
if (wsi->flags & LCCSCF_CACHE_COOKIES)
lws_cookie_send_cookies(wsi, (char **)&p, (char *)end);
#endif
if (wsi->a.protocol->callback(wsi,
LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER,
wsi->user_space, &p, lws_ptr_diff_size_t(end, p) - 12))

View file

@ -35,13 +35,18 @@ list(APPEND SOURCES
roles/http/header.c
roles/http/date.c
roles/http/parsers.c)
if (NOT LWS_WITHOUT_SERVER)
list(APPEND SOURCES
roles/http/server/server.c
roles/http/server/lws-spa.c)
endif()
if (LWS_WITH_CACHE_NSCOOKIEJAR AND LWS_WITH_CLIENT)
list(APPEND SOURCES
roles/http/cookie.c)
endif()
if (LWS_WITH_HTTP_PROXY AND LWS_WITH_HUBBUB)
list(APPEND SOURCES
roles/http/server/rewrite.c)

View file

@ -635,6 +635,13 @@ lws_client_interpret_server_handshake(struct lws *wsi)
ah->http_response = 0;
}
#if defined(LWS_WITH_CACHE_NSCOOKIEJAR) && defined(LWS_WITH_CLIENT)
if ((wsi->flags & LCCSCF_CACHE_COOKIES) &&
lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_SET_COOKIE))
lws_parse_set_cookie(wsi);
#endif
/*
* well, what the server sent looked reasonable for syntax.
* Now let's confirm it sent all the necessary headers
@ -1269,6 +1276,11 @@ lws_generate_client_handshake(struct lws *wsi, char *pkt)
/* give userland a chance to append, eg, cookies */
#if defined(LWS_WITH_CACHE_NSCOOKIEJAR) && defined(LWS_WITH_CLIENT)
if (wsi->flags & LCCSCF_CACHE_COOKIES)
lws_cookie_send_cookies(wsi, &p, end);
#endif
if (wsi->a.protocol->callback(wsi,
LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER,
wsi->user_space, &p,

729
lib/roles/http/cookie.c Normal file
View file

@ -0,0 +1,729 @@
#include <libwebsockets.h>
#include "private-lib-core.h"
//#define LWS_COOKIE_DEBUG
#if defined(LWS_COOKIE_DEBUG)
#define lwsl_cookie lwsl_notice
#else
#define lwsl_cookie lwsl_debug
#endif
#define LWS_COOKIE_MAX_CACHE_NAME_LEN 128
#define lws_tolower(_c) (((_c) >= 'A' && (_c) <= 'Z') ? \
(char)((_c) + 'a' - 'A') : \
(char)(_c))
#define LWS_COOKIE_NSC_FORMAT "%.*s\t"\
"%s\t"\
"%.*s\t"\
"%s\t"\
"%llu\t"\
"%.*s\t"\
"%.*s"
static const char *const mon = "janfebmaraprnayjunjulaugsepoctnovdec";
enum lws_cookie_nsc_f {
LWSC_NSC_DOMAIN,
LWSC_NSC_HOSTONLY,
LWSC_NSC_PATH,
LWSC_NSC_SECURE,
LWSC_NSC_EXPIRES,
LWSC_NSC_NAME,
LWSC_NSC_VALUE,
LWSC_NSC_COUNT,
};
enum lws_cookie_elements {
CE_DOMAIN,
CE_PATH,
CE_EXPIRES,
CE_MAXAGE,
CE_NAME,
CE_VALUE,
CE_HOSTONLY, /* these are bool, NULL = 0, non-NULL = 1 */
CE_SECURE,
CE_COUNT
};
struct lws_cookie {
const char *f[CE_COUNT];
size_t l[CE_COUNT];
unsigned int httponly:1;
};
static int
lws_cookie_parse_date(const char *d, size_t len, time_t *t)
{
struct tm date;
int offset = 0, i;
memset(&date, 0, sizeof(date));
while (len) {
if (isalnum((int)*d)) {
offset++;
goto next;
}
switch (offset) {
case 2:
if (*d == ':' && len >= 6) {
date.tm_hour = atoi(d - 2);
if (date.tm_hour < 0 || date.tm_hour > 23)
return -1;
date.tm_min = atoi(d + 1);
if (date.tm_min < 0 || date.tm_min > 60)
return -1;
date.tm_sec = atoi(d + 4);
if (date.tm_sec < 0 || date.tm_sec > 61)
/* leap second */
return -1;
d += 6;
len -= 6;
offset = 0;
continue;
}
if (!date.tm_mday) {
date.tm_mday = atoi(d - 2);
if (date.tm_mday < 1 || date.tm_mday > 31)
return -1;
goto next2;
}
if (!date.tm_year) {
date.tm_year = atoi(d - 2);
if (date.tm_year < 0 || date.tm_year > 99)
return -1;
if (date.tm_year < 70)
date.tm_year += 100;
}
goto next2;
case 3:
for (i = 0; i < 36; i += 3) {
if (lws_tolower(*(d - 3)) == mon[i] &&
lws_tolower(*(d - 2)) == mon[i + 1] &&
lws_tolower(*(d - 1)) == mon[i + 2]) {
date.tm_mon = i / 3;
break;
}
}
goto next2;
case 4:
if (!date.tm_year) {
date.tm_year = atoi(d - 4);
if (date.tm_year < 1601)
return -1;
date.tm_year -= 1900;
}
goto next2;
default:
goto next2;
}
next2:
offset = 0;
next:
d++;
len--;
}
*t = mktime(&date);
if (*t < 0)
return -1;
return 0;
}
static void
lws_cookie_rm_sws(const char **buf_p, size_t *len_p)
{
const char *buf;
size_t len;
if (!buf_p || !*buf_p || !len_p || !*len_p) {
lwsl_err("%s: false parameter\n", __func__);
return;
}
buf = *buf_p;
len = *len_p;
while (buf[0] == ' ' && len > 0) {
buf++;
len--;
}
while (buf[len - 1] == ' ' && len > 0)
len--;
*buf_p = buf;
*len_p = len;
}
static int
is_iprefix(const char *h, size_t hl, const char *n, size_t nl)
{
if (!h || !n || nl > hl)
return 0;
while (nl) {
nl--;
if (lws_tolower(h[nl]) != lws_tolower(n[nl]))
return 0;
}
return 1;
}
static int
lws_cookie_compile_cache_name(char *buf, size_t buf_len, struct lws_cookie *c)
{
if (!buf || !c->f[CE_DOMAIN] || !c->f[CE_PATH] || !c->f[CE_NAME] ||
c->l[CE_DOMAIN] + c->l[CE_PATH] + c->l[CE_NAME] + 6 > buf_len)
return -1;
memcpy(buf, c->f[CE_DOMAIN], c->l[CE_DOMAIN]);
buf += c->l[CE_DOMAIN];
*buf++ = '|';
memcpy(buf, c->f[CE_PATH], c->l[CE_PATH]);
buf += c->l[CE_PATH];
*buf++ = '|';
memcpy(buf, c->f[CE_NAME], c->l[CE_NAME]);
buf += c->l[CE_NAME];
*buf = '\0';
return 0;
}
static int
lws_cookie_parse_nsc(struct lws_cookie *c, const char *b, size_t l)
{
enum lws_cookie_nsc_f state = LWSC_NSC_DOMAIN;
size_t n = 0;
if (!c || !b || l < 13)
return -1;
memset(c, 0, sizeof(*c));
lwsl_cookie("%s: parsing (%.*s) \n", __func__, (int)l, b);
while (l) {
l--;
if (b[n] != '\t' && l) {
n++;
continue;
}
switch (state) {
case LWSC_NSC_DOMAIN:
c->f[CE_DOMAIN] = b;
c->l[CE_DOMAIN] = n;
break;
case LWSC_NSC_PATH:
c->f[CE_PATH] = b;
c->l[CE_PATH] = n;
break;
case LWSC_NSC_EXPIRES:
c->f[CE_EXPIRES] = b;
c->l[CE_EXPIRES] = n;
break;
case LWSC_NSC_NAME:
c->f[CE_NAME] = b;
c->l[CE_NAME] = n;
break;
case LWSC_NSC_HOSTONLY:
if (b[0] == 'T') {
c->f[CE_HOSTONLY] = b;
c->l[CE_HOSTONLY] = 1;
}
break;
case LWSC_NSC_SECURE:
if (b[0] == 'T') {
c->f[CE_SECURE] = b;
c->l[CE_SECURE] = 1;
}
break;
case LWSC_NSC_VALUE:
c->f[CE_VALUE] = b;
c->l[CE_VALUE] = n + 1;
for (n = 0; n < LWS_ARRAY_SIZE(c->f); n++)
lwsl_cookie("%s: %d: %.*s\n", __func__,
(int)n, (int)c->l[n], c->f[n]);
return 0;
default:
return -1;
}
b += n + 1;
n = 0;
state++;
}
return -1;
}
static int
lws_cookie_write_nsc(struct lws *wsi, struct lws_cookie *c)
{
char cache_name[LWS_COOKIE_MAX_CACHE_NAME_LEN];
struct lws_cache_ttl_lru *l1;
struct client_info_stash *stash;
char *cookie_string = NULL, *dl;
/* 6 tabs + 20 for max time_t + 2 * TRUE/FALSE + null */
size_t size = 6 + 20 + 10 + 1;
time_t expires = 0;
int ret = 0;
if (!wsi || !c)
return -1;
l1 = wsi->a.context->l1;
if (!l1 || !wsi->a.context->nsc)
return -1;
stash = wsi->stash ? wsi->stash : lws_get_network_wsi(wsi)->stash;
if (!stash || !stash->cis[CIS_ADDRESS] ||
!stash->cis[CIS_PATH])
return -1;
if (!c->f[CE_NAME] || !c->f[CE_VALUE]) {
lwsl_err("%s: malformed c\n", __func__);
return -1;
}
if (!c->f[CE_EXPIRES]) {
/*
* Currently we just take the approach to reject session cookies
*/
lwsl_warn("%s: reject session cookies\n", __func__);
return 0;
}
if (!c->f[CE_DOMAIN]) {
c->f[CE_HOSTONLY] = "T";
c->l[CE_HOSTONLY] = 1;
c->f[CE_DOMAIN] = stash->cis[CIS_ADDRESS];
c->l[CE_DOMAIN] = strlen(c->f[CE_DOMAIN]);
}
if (!c->f[CE_PATH]) {
c->f[CE_PATH] = stash->cis[CIS_PATH];
c->l[CE_PATH] = strlen(c->f[CE_PATH]);
dl = memchr(c->f[CE_PATH], '?', c->l[CE_PATH]);
if (dl)
c->l[CE_PATH] = (size_t)(dl - c->f[CE_PATH]);
}
if (lws_cookie_compile_cache_name(cache_name, sizeof(cache_name), c))
return -1;
if (c->f[CE_EXPIRES] &&
lws_cookie_parse_date(c->f[CE_EXPIRES], c->l[CE_EXPIRES], &expires)) {
lwsl_err("%s: can't parse date %.*s\n", __func__,
(int)c->l[CE_EXPIRES], c->f[CE_EXPIRES]);
return -1;
}
size += c->l[CE_NAME] + c->l[CE_VALUE] + c->l[CE_DOMAIN] + c->l[CE_PATH];
cookie_string = (char *)lws_malloc(size, __func__);
if (!cookie_string) {
lwsl_err("%s: OOM\n",__func__);
return -1;
}
lws_snprintf(cookie_string, size, LWS_COOKIE_NSC_FORMAT,
(int)c->l[CE_DOMAIN], c->f[CE_DOMAIN],
c->f[CE_HOSTONLY] ? "TRUE" : "FALSE",
(int)c->l[CE_PATH], c->f[CE_PATH],
c->f[CE_SECURE] ? "TRUE" : "FALSE",
(unsigned long long)expires,
(int)c->l[CE_NAME], c->f[CE_NAME],
(int)c->l[CE_VALUE], c->f[CE_VALUE]);
lwsl_cookie("%s: name %s\n", __func__, cache_name);
lwsl_cookie("%s: c %s\n", __func__, cookie_string);
if (lws_cache_write_through(l1, cache_name,
(const uint8_t *)cookie_string,
strlen(cookie_string),
(lws_usec_t)((unsigned long long)expires *
(lws_usec_t)LWS_US_PER_SEC), NULL)) {
ret = -1;
goto exit;
}
#if defined(LWS_COOKIE_DEBUG)
char *po;
if (lws_cache_item_get(l1, cache_name, (const void **)&po, &size) ||
size != strlen(cookie_string) || memcmp(po, cookie_string, size)) {
lwsl_err("%s: L1 '%s' missing\n", __func__, cache_name);
}
if (lws_cache_item_get(wsi->a.context->nsc, cache_name,
(const void **)&po, &size) ||
size != strlen(cookie_string) ||
memcmp(po, cookie_string, size)) {
lwsl_err("%s: NSC '%s' missing, size %llu, po %s\n", __func__,
cache_name, (unsigned long long)size, po);
}
#endif
exit:
lws_free(cookie_string);
return ret;
}
static int
lws_cookie_attach_cookies(struct lws *wsi, char *buf, char *end)
{
const char *domain, *path, *dl_domain, *dl_path, *po;
char cache_name[LWS_COOKIE_MAX_CACHE_NAME_LEN];
size_t domain_len, path_len, size, ret = 0;
struct lws_cache_ttl_lru *l1;
struct client_info_stash *stash;
lws_cache_results_t cr;
struct lws_cookie c;
int hostdomain = 1;
char *p, *p1;
if (!wsi)
return -1;
stash = wsi->stash ? wsi->stash : lws_get_network_wsi(wsi)->stash;
if (!stash || !stash->cis[CIS_ADDRESS] ||
!stash->cis[CIS_PATH])
return -1;
l1 = wsi->a.context->l1;
if (!l1 || !wsi->a.context->nsc){
lwsl_err("%s:no cookiejar\n", __func__);
return -1;
}
memset(&c, 0, sizeof(c));
domain = stash->cis[CIS_ADDRESS];
path = stash->cis[CIS_PATH];
if (!domain || !path)
return -1;
path_len = strlen(path);
/* remove query string if exist */
dl_path = memchr(path, '?', path_len);
if (dl_path)
path_len = lws_ptr_diff_size_t(dl_path, path);
/* remove last slash if exist */
if (path_len != 1 && path[path_len - 1] == '/')
path_len--;
if (!path_len)
return -1;
lwsl_cookie("%s: path %.*s len %d\n", __func__, (int)path_len, path, (int)path_len);
/* when dest buf is not provided, we only return size of cookie string */
if (!buf || !end)
p = NULL;
else
p = buf;
/* iterate through domain and path levels to find matching cookies */
dl_domain = domain;
while (dl_domain) {
domain_len = strlen(domain);
dl_domain = memchr(domain, '.', domain_len);
/* don't match top level domain */
if (!dl_domain)
break;
if (domain_len + path_len + 6 > sizeof(cache_name))
return -1;
/* compile key string "[domain]|[path]|*"" */
p1 = cache_name;
memcpy(p1, domain, domain_len);
p1 += domain_len;
*p1 = '|';
p1++;
memcpy(p1, path, path_len);
p1 += path_len;
*p1 = '|';
p1++;
*p1 = '*';
p1++;
*p1 = '\0';
lwsl_cookie("%s: looking for %s\n", __func__, cache_name);
if (!lws_cache_lookup(l1, cache_name,
(const void **)&cr.ptr, &cr.size)) {
while (!lws_cache_results_walk(&cr)) {
lwsl_cookie(" %s (%d)\n", (const char *)cr.tag,
(int)cr.payload_len);
if (lws_cache_item_get(l1, (const char *)cr.tag,
(const void **)&po, &size) ||
lws_cookie_parse_nsc(&c, po, size)) {
lwsl_err("%s: failed to get c '%s'\n",
__func__, cr.tag);
break;
}
if (c.f[CE_HOSTONLY] && !hostdomain){
lwsl_cookie("%s: not sending this\n",
__func__);
continue;
}
if (p) {
if (ret) {
*p = ';';
p++;
*p = ' ';
p++;
}
memcpy(p, c.f[CE_NAME], c.l[CE_NAME]);
p += c.l[CE_NAME];
*p = '=';
p++;
memcpy(p, c.f[CE_VALUE], c.l[CE_VALUE]);
p += c.l[CE_VALUE];
}
if (ret)
ret += 2;
ret += c.l[CE_NAME] + 1 + c.l[CE_VALUE];
}
}
domain = dl_domain + 1;
hostdomain = 0;
}
lwsl_notice("%s: c len (%d)\n", __func__, (int)ret);
return (int)ret;
}
static struct {
const char *const name;
uint8_t len;
} cft[] = {
{ "domain=", 7 },
{ "path=", 5 },
{ "expires=", 8 },
{ "max-age=", 8 },
{ "httponly", 8 },
{ "secure", 6 }
};
int
lws_parse_set_cookie(struct lws *wsi)
{
char *tk_head, *tk_end, *buf_head, *buf_end, *cookiep, *dl;
struct lws_cache_ttl_lru *l1;
struct lws_cookie c;
size_t fl;
int f, n;
if (!wsi)
return -1;
l1 = wsi->a.context->l1;
if (!l1)
return -1;
f = wsi->http.ah->frag_index[WSI_TOKEN_HTTP_SET_COOKIE];
while (f) {
cookiep = wsi->http.ah->data + wsi->http.ah->frags[f].offset;
fl = wsi->http.ah->frags[f].len;
f = wsi->http.ah->frags[f].nfrag;
if (!cookiep || !fl)
continue;
#if defined(LWS_COOKIE_DEBUG)
lwsl_notice("%s:parsing: %.*s\n", __func__, (int)fl, cookiep);
#endif
buf_head = cookiep;
buf_end = cookiep + fl - 1;
memset(&c, 0, sizeof(struct lws_cookie));
do {
tk_head = buf_head;
tk_end = memchr(buf_head, ';',
(size_t)(buf_end - buf_head + 1));
if (!tk_end) {
tk_end = buf_end;
buf_head = buf_end;
} else {
buf_head = tk_end + 1;
tk_end--;
}
if (c.f[CE_NAME])
goto parse_av;
/*
* find name value, remove leading trailing
* WS and DQ for value
*/
dl = memchr(tk_head, '=', lws_ptr_diff_size_t(tk_end,
tk_head + 1));
if (!dl || dl == tk_head)
return -1;
c.f[CE_NAME] = tk_head;
c.l[CE_NAME] = lws_ptr_diff_size_t(dl, tk_head);
lws_cookie_rm_sws(&c.f[CE_NAME], &c.l[CE_NAME]);
if (!c.l[CE_NAME])
return -1;
lwsl_cookie("%s: c name l %d v:%.*s\n", __func__,
(int)c.l[CE_NAME],
(int)c.l[CE_NAME], c.f[CE_NAME]);
c.f[CE_VALUE] = dl + 1;
c.l[CE_VALUE] = lws_ptr_diff_size_t(tk_end,
c.f[CE_VALUE]) + 1;
lws_cookie_rm_sws(&c.f[CE_VALUE], &c.l[CE_VALUE]);
if (c.l[CE_VALUE] >= 2 && c.f[CE_VALUE][0] == '\"') {
c.f[CE_VALUE]++;
c.l[CE_VALUE] -= 2;
}
lwsl_cookie("%s: c value l %d v:%.*s\n", __func__,
(int)c.l[CE_VALUE], (int)c.l[CE_VALUE],
c.f[CE_VALUE]);
continue;
parse_av:
while (*tk_head == ' ') {
if (tk_head == tk_end)
return -1;
tk_head++;
}
for (n = 0; n < (int)LWS_ARRAY_SIZE(cft); n++) {
if (lws_tolower(*tk_head) != cft[n].name[0])
continue;
if (!is_iprefix(tk_head,
lws_ptr_diff_size_t(tk_end,
tk_head) + 1,
cft[n].name, cft[n].len))
continue;
if (n == 4 || n == 5) {
c.f[n] = "T";
c.l[n] = 1;
break;
}
c.f[n] = tk_head + cft[n].len;
c.l[n] = lws_ptr_diff_size_t(tk_end, c.f[n]) + 1;
lws_cookie_rm_sws(&c.f[n], &c.l[n]);
if (n == CE_DOMAIN && c.l[0] &&
c.f[n][0] == '.'){
c.f[n]++;
c.l[n]--;
}
lwsl_cookie("%s: %s l %d v:%.*s\n", __func__,
cft[n].name, (int)c.l[n],
(int)c.l[n], c.f[n]);
break;
}
} while (tk_end != buf_end);
if (lws_cookie_write_nsc(wsi, &c))
lwsl_err("%s:failed to write nsc\n", __func__);
}
return 0;
}
int
lws_cookie_send_cookies(struct lws *wsi, char **pp, char *end)
{
char *p;
int size;
if (!wsi || !pp || !(*pp) || !end)
return -1;
size = lws_cookie_attach_cookies(wsi, NULL, NULL);
if (!size)
return 0;
if (size < 0) {
lwsl_err("%s:failed to get cookie string size\n", __func__);
return -1;
}
lwsl_notice("%s: size %d\n", __func__, size);
#if defined(LWS_COOKIE_DEBUG)
char *p_dbg = *pp;
#endif
if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_COOKIE, NULL, size,
(unsigned char **)pp, (unsigned char *)end))
return -1;
#if defined(LWS_COOKIE_DEBUG)
lwsl_notice("%s: dummy copy (%.*s) \n", __func__, (int)(*pp - p_dbg), p_dbg);
#endif
#ifdef LWS_WITH_HTTP2
if (lws_wsi_is_h2(wsi))
p = *pp - size;
else
#endif
p = *pp - size - 2;
if (lws_cookie_attach_cookies(wsi, p, p + size) <= 0) {
lwsl_err("%s:failed to attach cookies\n", __func__);
return -1;
}
#if defined(LWS_COOKIE_DEBUG)
lwsl_notice("%s: real copy (%.*s) total len %d\n", __func__, (int)(*pp - p_dbg), p_dbg, (int)(*pp - p_dbg));
lwsl_hexdump_notice(p_dbg, (size_t)(*pp - p_dbg));
#endif
return 0;
}

View file

@ -52,7 +52,7 @@ lws_http_string_to_known_header(const char *s, size_t slen)
}
#ifdef LWS_WITH_HTTP2
static int
int
lws_wsi_is_h2(struct lws *wsi)
{
return wsi->upgraded_to_http2 ||
@ -87,7 +87,8 @@ lws_add_http_header_by_name(struct lws *wsi, const unsigned char *name,
if (*p + length + 3 >= end)
return 1;
memcpy(*p, value, (unsigned int)length);
if (value)
memcpy(*p, value, (unsigned int)length);
*p += length;
*((*p)++) = '\x0d';
*((*p)++) = '\x0a';

View file

@ -621,6 +621,10 @@ The `content-type` to mark up the multipart mime section with if present
Indicate the data is sent in `x-www-form-urlencoded` form
### `http_cookies`
This streamtype should store and bring out http cookies from the peer.
### `rideshare`
For special cases where one logically separate stream travels with another when using this

View file

@ -98,6 +98,7 @@ static const char * const lejp_tokens_policy[] = {
"s[].*.http_mime_content_type",
"s[].*.http_www_form_urlencoded",
"s[].*.http_expect",
"s[].*.http_cookies",
"s[].*.http_fail_redirect",
"s[].*.http_multipart_ss_in",
"s[].*.ws_subprotocol",
@ -199,6 +200,7 @@ typedef enum {
LSSPPT_HTTP_MULTIPART_CONTENT_TYPE,
LSSPPT_HTTP_WWW_FORM_URLENCODED,
LSSPPT_HTTP_EXPECT,
LSSPPT_HTTP_COOKIES,
LSSPPT_HTTP_FAIL_REDIRECT,
LSSPPT_HTTP_MULTIPART_SS_IN,
LSSPPT_WS_SUBPROTOCOL,
@ -771,6 +773,11 @@ lws_ss_policy_parser_cb(struct lejp_ctx *ctx, char reason)
a->curr[LTY_POLICY].p->flags |=
LWSSSPOLF_ALLOW_REDIRECTS;
break;
case LSSPPT_HTTP_COOKIES:
if (reason == LEJPCB_VAL_TRUE)
a->curr[LTY_POLICY].p->flags |=
LWSSSPOLF_HTTP_CACHE_COOKIES;
break;
case LSSPPT_HTTP_MULTIPART_SS_IN:
if (reason == LEJPCB_VAL_TRUE)
a->curr[LTY_POLICY].p->flags |=

View file

@ -1149,6 +1149,9 @@ secstream_connect_munge_h1(lws_ss_handle_t *h, char *buf, size_t len,
i->ssl_connection |= LCCSCF_HTTP_X_WWW_FORM_URLENCODED;
#endif
if (h->policy->flags & LWSSSPOLF_HTTP_CACHE_COOKIES)
i->ssl_connection |= LCCSCF_CACHE_COOKIES;
/* protocol aux is the path part */
i->path = buf;

View file

@ -158,6 +158,9 @@ secstream_connect_munge_h2(lws_ss_handle_t *h, char *buf, size_t len,
if (h->policy->flags & LWSSSPOLF_HTTP_X_WWW_FORM_URLENCODED)
i->ssl_connection |= LCCSCF_HTTP_X_WWW_FORM_URLENCODED;
if (h->policy->flags & LWSSSPOLF_HTTP_CACHE_COOKIES)
i->ssl_connection |= LCCSCF_CACHE_COOKIES;
i->ssl_connection |= LCCSCF_PIPELINE;
i->alpn = "h2";

View file

@ -222,6 +222,9 @@ secstream_connect_munge_ws(lws_ss_handle_t *h, char *buf, size_t len,
if (!pbasis)
return 0;
if (h->policy->flags & LWSSSPOLF_HTTP_CACHE_COOKIES)
i->ssl_connection |= LCCSCF_CACHE_COOKIES;
/* protocol aux is the path part ; ws subprotocol name */
i->path = buf;

View file

@ -170,7 +170,7 @@ static int
test_nsc1(void)
{
struct lws_cache_creation_info ci;
struct lws_cache_ttl_lru *l1, *nsc;
struct lws_cache_ttl_lru *l1 = NULL, *nsc;
lws_cache_results_t cr;
int n, ret = 1;
size_t size;

View file

@ -21,6 +21,8 @@ Commandline option|Meaning
-j|Apply tls option LCCSCF_ALLOW_SELFSIGNED
-m|Apply tls option LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK
-e|Apply tls option LCCSCF_ALLOW_EXPIRED
-b|Apply tls option LCCSCF_CACHE_COOKIES
-c <cookie jar file>|Set filepath used for cookie jar
-v|Connection validity use 3s / 10s instead of default 5m / 5m10s
--nossl| disable ssl connection
--user <username>| Set Basic Auth username

View file

@ -279,6 +279,9 @@ system_notify_cb(lws_state_manager_t *mgr, lws_state_notify_link_t *link,
if (lws_cmdline_option(a->argc, a->argv, "-k"))
i.ssl_connection |= LCCSCF_ALLOW_INSECURE;
if (lws_cmdline_option(a->argc, a->argv, "-b"))
i.ssl_connection |= LCCSCF_CACHE_COOKIES;
if (lws_cmdline_option(a->argc, a->argv, "-m"))
i.ssl_connection |= LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK;
@ -362,6 +365,12 @@ int main(int argc, const char **argv)
info.register_notifier_list = na;
info.connect_timeout_secs = 30;
#if defined(LWS_WITH_CACHE_NSCOOKIEJAR)
info.http_nsc_filepath = "./cookies.txt";
if ((p = lws_cmdline_option(argc, argv, "-c")))
info.http_nsc_filepath = p;
#endif
/*
* since we know this lws context is only ever going to be used with
* one client wsis / fds / sockets at a time, let lws know it doesn't