diff --git a/.sai.json b/.sai.json index a4ae1e189..1365d5cb1 100644 --- a/.sai.json +++ b/.sai.json @@ -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" }, diff --git a/CMakeLists.txt b/CMakeLists.txt index 4718a438d..afdd5cac7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/READMEs/README.http-cache.md b/READMEs/README.http-cache.md new file mode 100644 index 000000000..a13783623 --- /dev/null +++ b/READMEs/README.http-cache.md @@ -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 +``` diff --git a/include/libwebsockets/lws-client.h b/include/libwebsockets/lws-client.h index cb307f8ff..8727cf56b 100644 --- a/include/libwebsockets/lws-client.h +++ b/include/libwebsockets/lws-client.h @@ -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 diff --git a/include/libwebsockets/lws-context-vhost.h b/include/libwebsockets/lws-context-vhost.h index 8e1fed53e..522a8fad3 100644 --- a/include/libwebsockets/lws-context-vhost.h +++ b/include/libwebsockets/lws-context-vhost.h @@ -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 * diff --git a/include/libwebsockets/lws-secure-streams-policy.h b/include/libwebsockets/lws-secure-streams-policy.h index 4b782ae6d..887295cd9 100644 --- a/include/libwebsockets/lws-secure-streams-policy.h +++ b/include/libwebsockets/lws-secure-streams-policy.h @@ -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 */ }; diff --git a/lib/core/context.c b/lib/core/context.c index c7e84b41b..b54459b86 100644 --- a/lib/core/context.c +++ b/lib/core/context.c @@ -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 diff --git a/lib/core/private-lib-core.h b/lib/core/private-lib-core.h index 1b5225209..459b923e6 100644 --- a/lib/core/private-lib-core.h +++ b/lib/core/private-lib-core.h @@ -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 */ diff --git a/lib/misc/cache-ttl/file.c b/lib/misc/cache-ttl/file.c index 58a4b6942..3307faf8e 100644 --- a/lib/misc/cache-ttl/file.c +++ b/lib/misc/cache-ttl/file.c @@ -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); diff --git a/lib/misc/cache-ttl/heap.c b/lib/misc/cache-ttl/heap.c index 1ea7edfb1..4fc16923a 100644 --- a/lib/misc/cache-ttl/heap.c +++ b/lib/misc/cache-ttl/heap.c @@ -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); } diff --git a/lib/roles/h2/hpack.c b/lib/roles/h2/hpack.c index 68629e6f4..f6aefd34d 100644 --- a/lib/roles/h2/hpack.c +++ b/lib/roles/h2/hpack.c @@ -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; diff --git a/lib/roles/h2/http2.c b/lib/roles/h2/http2.c index 9ae46d953..21b605cc4 100644 --- a/lib/roles/h2/http2.c +++ b/lib/roles/h2/http2.c @@ -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)) diff --git a/lib/roles/http/CMakeLists.txt b/lib/roles/http/CMakeLists.txt index 706823abe..01ad79b9d 100644 --- a/lib/roles/http/CMakeLists.txt +++ b/lib/roles/http/CMakeLists.txt @@ -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) diff --git a/lib/roles/http/client/client-http.c b/lib/roles/http/client/client-http.c index 99f270b6a..221f394de 100644 --- a/lib/roles/http/client/client-http.c +++ b/lib/roles/http/client/client-http.c @@ -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, diff --git a/lib/roles/http/cookie.c b/lib/roles/http/cookie.c new file mode 100644 index 000000000..03b88ae0b --- /dev/null +++ b/lib/roles/http/cookie.c @@ -0,0 +1,729 @@ + +#include +#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; +} diff --git a/lib/roles/http/header.c b/lib/roles/http/header.c index 83983a573..b0d54776f 100644 --- a/lib/roles/http/header.c +++ b/lib/roles/http/header.c @@ -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'; diff --git a/lib/secure-streams/README.md b/lib/secure-streams/README.md index 96978d7f3..1892b9788 100644 --- a/lib/secure-streams/README.md +++ b/lib/secure-streams/README.md @@ -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 diff --git a/lib/secure-streams/policy-json.c b/lib/secure-streams/policy-json.c index da3a86e0e..99d6de464 100644 --- a/lib/secure-streams/policy-json.c +++ b/lib/secure-streams/policy-json.c @@ -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 |= diff --git a/lib/secure-streams/protocols/ss-h1.c b/lib/secure-streams/protocols/ss-h1.c index 5f1c29287..8db5a41f7 100644 --- a/lib/secure-streams/protocols/ss-h1.c +++ b/lib/secure-streams/protocols/ss-h1.c @@ -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; diff --git a/lib/secure-streams/protocols/ss-h2.c b/lib/secure-streams/protocols/ss-h2.c index 96a0fa3c5..3d1aa9c23 100644 --- a/lib/secure-streams/protocols/ss-h2.c +++ b/lib/secure-streams/protocols/ss-h2.c @@ -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"; diff --git a/lib/secure-streams/protocols/ss-ws.c b/lib/secure-streams/protocols/ss-ws.c index 39bdda1e8..6757f0ad0 100644 --- a/lib/secure-streams/protocols/ss-ws.c +++ b/lib/secure-streams/protocols/ss-ws.c @@ -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; diff --git a/minimal-examples/api-tests/api-test-lws_cache/main.c b/minimal-examples/api-tests/api-test-lws_cache/main.c index c5c2099f6..64835fff8 100644 --- a/minimal-examples/api-tests/api-test-lws_cache/main.c +++ b/minimal-examples/api-tests/api-test-lws_cache/main.c @@ -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; diff --git a/minimal-examples/http-client/minimal-http-client/README.md b/minimal-examples/http-client/minimal-http-client/README.md index 9387f8c75..c37549cf3 100644 --- a/minimal-examples/http-client/minimal-http-client/README.md +++ b/minimal-examples/http-client/minimal-http-client/README.md @@ -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 |Set filepath used for cookie jar -v|Connection validity use 3s / 10s instead of default 5m / 5m10s --nossl| disable ssl connection --user | Set Basic Auth username diff --git a/minimal-examples/http-client/minimal-http-client/minimal-http-client.c b/minimal-examples/http-client/minimal-http-client/minimal-http-client.c index b1fddf050..246b18325 100644 --- a/minimal-examples/http-client/minimal-http-client/minimal-http-client.c +++ b/minimal-examples/http-client/minimal-http-client/minimal-http-client.c @@ -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