#include "curl_setup.h"
#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES)
#include "urldata.h"
#include "cookie.h"
#include "psl.h"
#include "curl_trc.h"
#include "slist.h"
#include "curl_share.h"
#include "strcase.h"
#include "curl_fopen.h"
#include "curl_get_line.h"
#include "curl_memrchr.h"
#include "parsedate.h"
#include "strdup.h"
#include "llist.h"
#include "bufref.h"
#include "curlx/strparse.h"
#define COOKIES_MAXAGE (400 * 24 * 3600)
static void cap_expires(time_t now, struct Cookie *co)
{
if(co->expires && (TIME_T_MAX - COOKIES_MAXAGE - 30) > now) {
timediff_t cap = now + COOKIES_MAXAGE;
if(co->expires > cap) {
cap += 30;
co->expires = (cap / 60) * 60;
}
}
}
static void freecookie(struct Cookie *co, bool maintoo)
{
curlx_free(co->domain);
curlx_free(co->path);
curlx_free(co->name);
curlx_free(co->value);
if(maintoo)
curlx_free(co);
}
static bool cookie_tailmatch(const char *cookie_domain,
size_t cookie_domain_len,
const char *hostname)
{
size_t hostname_len = strlen(hostname);
if(hostname_len < cookie_domain_len)
return FALSE;
if(!curl_strnequal(cookie_domain,
hostname + hostname_len - cookie_domain_len,
cookie_domain_len))
return FALSE;
if(hostname_len == cookie_domain_len)
return TRUE;
if('.' == *(hostname + hostname_len - cookie_domain_len - 1))
return TRUE;
return FALSE;
}
static bool pathmatch(const char *cookie_path, const char *uri_path)
{
size_t cookie_path_len;
size_t uri_path_len;
bool ret = FALSE;
cookie_path_len = strlen(cookie_path);
if(cookie_path_len == 1) {
return TRUE;
}
if(strlen(uri_path) == 0 || uri_path[0] != '/')
uri_path = "/";
uri_path_len = strlen(uri_path);
if(uri_path_len < cookie_path_len)
goto pathmatched;
if(strncmp(cookie_path, uri_path, cookie_path_len))
goto pathmatched;
if(cookie_path_len == uri_path_len) {
ret = TRUE;
goto pathmatched;
}
if(uri_path[cookie_path_len] == '/') {
ret = TRUE;
goto pathmatched;
}
pathmatched:
return ret;
}
static const char *get_top_domain(const char * const domain, size_t *outlen)
{
size_t len = 0;
const char *first = NULL, *last;
if(domain) {
len = strlen(domain);
last = memrchr(domain, '.', len);
if(last) {
first = memrchr(domain, '.', (last - domain));
if(first)
len -= (++first - domain);
}
}
if(outlen)
*outlen = len;
return first ? first : domain;
}
#if defined(_MSC_VER) && (_MSC_VER == 1900)
#pragma optimize("", off)
#endif
static size_t cookie_hash_domain(const char *domain, const size_t len)
{
const char *end = domain + len;
size_t h = 5381;
while(domain < end) {
size_t j = (size_t)Curl_raw_toupper(*domain++);
h += h << 5;
h ^= j;
}
return (h % COOKIE_HASH_SIZE);
}
#if defined(_MSC_VER) && (_MSC_VER == 1900)
#pragma optimize("", on)
#endif
static size_t cookiehash(const char * const domain)
{
const char *top;
size_t len;
if(!domain || Curl_host_is_ipnum(domain))
return 0;
top = get_top_domain(domain, &len);
return cookie_hash_domain(top, len);
}
static char *sanitize_cookie_path(const char *cookie_path, size_t len)
{
if(len && (cookie_path[0] == '\"')) {
cookie_path++;
len--;
if(len && (cookie_path[len - 1] == '\"'))
len--;
}
if(!len || (cookie_path[0] != '/'))
return curlx_strdup("/");
if(len > 1 && cookie_path[len - 1] == '/')
len--;
return Curl_memdup0(cookie_path, len);
}
static CURLcode strstore(char **str, const char *newstr, size_t len)
{
DEBUGASSERT(str);
if(!len) {
len++;
newstr = "";
}
*str = Curl_memdup0(newstr, len);
if(!*str)
return CURLE_OUT_OF_MEMORY;
return CURLE_OK;
}
static void remove_expired(struct CookieInfo *ci)
{
struct Cookie *co;
curl_off_t now = (curl_off_t)time(NULL);
unsigned int i;
if(now < ci->next_expiration &&
ci->next_expiration != CURL_OFF_T_MAX)
return;
else
ci->next_expiration = CURL_OFF_T_MAX;
for(i = 0; i < COOKIE_HASH_SIZE; i++) {
struct Curl_llist_node *n;
struct Curl_llist_node *e = NULL;
for(n = Curl_llist_head(&ci->cookielist[i]); n; n = e) {
co = Curl_node_elem(n);
e = Curl_node_next(n);
if(co->expires) {
if(co->expires < now) {
Curl_node_remove(n);
freecookie(co, TRUE);
ci->numcookies--;
}
else if(co->expires < ci->next_expiration)
ci->next_expiration = co->expires;
}
}
}
}
#ifndef USE_LIBPSL
static bool bad_domain(const char *domain, size_t len)
{
if((len == 9) && curl_strnequal(domain, "localhost", 9))
return FALSE;
else {
char *dot = memchr(domain, '.', len);
if(dot) {
size_t i = dot - domain;
if((len - i) > 1)
return FALSE;
}
}
return TRUE;
}
#endif
static bool invalid_octets(const char *ptr, size_t len)
{
const unsigned char *p = (const unsigned char *)ptr;
while(len && *p) {
if(((*p != 9) && (*p < 0x20)) || (*p == 0x7f))
return TRUE;
p++;
len--;
}
return FALSE;
}
#define MAX_DATE_LENGTH 80
#define COOKIE_NAME 0
#define COOKIE_VALUE 1
#define COOKIE_DOMAIN 2
#define COOKIE_PATH 3
#define COOKIE_PIECES 4
static CURLcode storecookie(struct Cookie *co, struct Curl_str *cp,
const char *path, const char *domain)
{
CURLcode result;
result = strstore(&co->name, curlx_str(&cp[COOKIE_NAME]),
curlx_strlen(&cp[COOKIE_NAME]));
if(!result)
result = strstore(&co->value, curlx_str(&cp[COOKIE_VALUE]),
curlx_strlen(&cp[COOKIE_VALUE]));
if(!result) {
size_t plen = 0;
if(curlx_strlen(&cp[COOKIE_PATH])) {
path = curlx_str(&cp[COOKIE_PATH]);
plen = curlx_strlen(&cp[COOKIE_PATH]);
}
else if(path) {
const char *endslash = strrchr(path, '/');
if(endslash)
plen = (endslash - path + 1);
else
plen = strlen(path);
}
if(path) {
co->path = sanitize_cookie_path(path, plen);
if(!co->path)
result = CURLE_OUT_OF_MEMORY;
}
}
if(!result) {
if(curlx_strlen(&cp[COOKIE_DOMAIN]))
result = strstore(&co->domain, curlx_str(&cp[COOKIE_DOMAIN]),
curlx_strlen(&cp[COOKIE_DOMAIN]));
else if(domain) {
co->domain = curlx_strdup(domain);
if(!co->domain)
result = CURLE_OUT_OF_MEMORY;
}
}
return result;
}
static CURLcode
parse_cookie_header(struct Curl_easy *data,
struct Cookie *co,
struct CookieInfo *ci,
bool *okay,
const char *ptr,
const char *domain,
const char *path,
bool secure)
{
time_t now = 0;
size_t linelength = strlen(ptr);
CURLcode result = CURLE_OK;
struct Curl_str cookie[COOKIE_PIECES];
*okay = FALSE;
if(linelength > MAX_COOKIE_LINE)
return CURLE_OK;
memset(cookie, 0, sizeof(cookie));
do {
struct Curl_str name;
struct Curl_str val;
if(!curlx_str_cspn(&ptr, &name, ";\t\r\n=")) {
bool sep = FALSE;
curlx_str_trimblanks(&name);
if(!curlx_str_single(&ptr, '=')) {
sep = TRUE;
if(!curlx_str_cspn(&ptr, &val, ";\r\n"))
curlx_str_trimblanks(&val);
}
else
curlx_str_init(&val);
if(!curlx_strlen(&cookie[COOKIE_NAME])) {
if(!sep ||
invalid_octets(curlx_str(&name), curlx_strlen(&name)) ||
invalid_octets(curlx_str(&val), curlx_strlen(&val)) ||
!curlx_strlen(&name)) {
infof(data, "invalid octets in name/value, cookie dropped");
return CURLE_OK;
}
if(curlx_strlen(&name) >= (MAX_NAME - 1) ||
curlx_strlen(&val) >= (MAX_NAME - 1) ||
((curlx_strlen(&name) + curlx_strlen(&val)) > MAX_NAME)) {
infof(data, "oversized cookie dropped, name/val %zu + %zu bytes",
curlx_strlen(&name), curlx_strlen(&val));
return CURLE_OK;
}
if(curlx_strlen(&val) &&
memchr(curlx_str(&val), '\t', curlx_strlen(&val))) {
infof(data, "cookie contains TAB, dropping");
return CURLE_OK;
}
if(!strncmp("__Secure-", curlx_str(&name), 9))
co->prefix_secure = TRUE;
else if(!strncmp("__Host-", curlx_str(&name), 7))
co->prefix_host = TRUE;
cookie[COOKIE_NAME] = name;
cookie[COOKIE_VALUE] = val;
}
else if(!sep) {
if(curlx_str_casecompare(&name, "secure")) {
if(secure || !ci->running)
co->secure = TRUE;
else {
infof(data, "skipped cookie because not 'secure'");
return CURLE_OK;
}
}
else if(curlx_str_casecompare(&name, "httponly"))
co->httponly = TRUE;
}
else if(curlx_str_casecompare(&name, "path")) {
cookie[COOKIE_PATH] = val;
}
else if(curlx_str_casecompare(&name, "domain") && curlx_strlen(&val)) {
bool is_ip;
const char *v = curlx_str(&val);
if('.' == *v)
curlx_str_nudge(&val, 1);
#ifndef USE_LIBPSL
if(bad_domain(curlx_str(&val), curlx_strlen(&val)))
domain = ":";
#endif
is_ip = Curl_host_is_ipnum(domain ? domain : curlx_str(&val));
if(!domain ||
(is_ip &&
!strncmp(curlx_str(&val), domain, curlx_strlen(&val)) &&
(curlx_strlen(&val) == strlen(domain))) ||
(!is_ip && cookie_tailmatch(curlx_str(&val),
curlx_strlen(&val), domain))) {
cookie[COOKIE_DOMAIN] = val;
if(!is_ip)
co->tailmatch = TRUE;
}
else {
infof(data, "skipped cookie with bad tailmatch domain: %s",
curlx_str(&val));
return CURLE_OK;
}
}
else if(curlx_str_casecompare(&name, "max-age") && curlx_strlen(&val)) {
int rc;
const char *maxage = curlx_str(&val);
if(*maxage == '\"')
maxage++;
rc = curlx_str_number(&maxage, &co->expires, CURL_OFF_T_MAX);
if(!now)
now = time(NULL);
switch(rc) {
case STRE_OVERFLOW:
co->expires = CURL_OFF_T_MAX;
break;
default:
co->expires = 1;
break;
case STRE_OK:
if(!co->expires)
co->expires = 1;
else if(CURL_OFF_T_MAX - now < co->expires)
co->expires = CURL_OFF_T_MAX;
else
co->expires += now;
break;
}
cap_expires(now, co);
}
else if(curlx_str_casecompare(&name, "expires") && curlx_strlen(&val) &&
!co->expires && (curlx_strlen(&val) < MAX_DATE_LENGTH)) {
char dbuf[MAX_DATE_LENGTH + 1];
time_t date = 0;
memcpy(dbuf, curlx_str(&val), curlx_strlen(&val));
dbuf[curlx_strlen(&val)] = 0;
if(!Curl_getdate_capped(dbuf, &date)) {
if(!date)
date++;
co->expires = (curl_off_t)date;
}
else
co->expires = 0;
if(!now)
now = time(NULL);
cap_expires(now, co);
}
}
} while(!curlx_str_single(&ptr, ';'));
if(curlx_strlen(&cookie[COOKIE_NAME])) {
result = storecookie(co, &cookie[0], path, domain);
if(!result)
*okay = TRUE;
}
return result;
}
static CURLcode parse_netscape(struct Cookie *co,
struct CookieInfo *ci,
bool *okay,
const char *lineptr,
bool secure)
{
const char *ptr, *next;
int fields;
size_t len;
*okay = FALSE;
if(strncmp(lineptr, "#HttpOnly_", 10) == 0) {
lineptr += 10;
co->httponly = TRUE;
}
if(lineptr[0] == '#')
return CURLE_OK;
fields = 0;
for(next = lineptr; next; fields++) {
ptr = next;
len = strcspn(ptr, "\t\r\n");
next = (ptr[len] == '\t' ? &ptr[len + 1] : NULL);
switch(fields) {
case 0:
if(ptr[0] == '.') {
ptr++;
len--;
}
co->domain = Curl_memdup0(ptr, len);
if(!co->domain)
return CURLE_OUT_OF_MEMORY;
break;
case 1:
co->tailmatch = !!curl_strnequal(ptr, "TRUE", len);
break;
case 2:
if(strncmp("TRUE", ptr, len) && strncmp("FALSE", ptr, len)) {
co->path = sanitize_cookie_path(ptr, len);
if(!co->path)
return CURLE_OUT_OF_MEMORY;
break;
}
else {
co->path = curlx_strdup("/");
if(!co->path)
return CURLE_OUT_OF_MEMORY;
}
fields++;
FALLTHROUGH();
case 3:
co->secure = FALSE;
if(curl_strnequal(ptr, "TRUE", len)) {
if(secure || ci->running)
co->secure = TRUE;
else
return CURLE_OK;
}
break;
case 4:
if(curlx_str_number(&ptr, &co->expires, CURL_OFF_T_MAX))
return CURLE_OK;
break;
case 5:
co->name = Curl_memdup0(ptr, len);
if(!co->name)
return CURLE_OUT_OF_MEMORY;
else {
if(curl_strnequal("__Secure-", co->name, 9))
co->prefix_secure = TRUE;
else if(curl_strnequal("__Host-", co->name, 7))
co->prefix_host = TRUE;
}
break;
case 6:
co->value = Curl_memdup0(ptr, len);
if(!co->value)
return CURLE_OUT_OF_MEMORY;
break;
}
}
if(fields == 6) {
co->value = curlx_strdup("");
if(!co->value)
return CURLE_OUT_OF_MEMORY;
else
fields++;
}
if(fields != 7)
return CURLE_OK;
*okay = TRUE;
return CURLE_OK;
}
static bool is_public_suffix(struct Curl_easy *data,
struct Cookie *co,
const char *domain)
{
#ifdef USE_LIBPSL
DEBUGF(infof(data, "PSL check set-cookie '%s' for domain=%s in %s",
co->name, co->domain, domain));
if(data && (domain && co->domain && !Curl_host_is_ipnum(co->domain))) {
bool acceptable = FALSE;
char lcase[256];
char lcookie[256];
size_t dlen = strlen(domain);
size_t clen = strlen(co->domain);
if((dlen < sizeof(lcase)) && (clen < sizeof(lcookie))) {
const psl_ctx_t *psl = Curl_psl_use(data);
if(psl) {
Curl_strntolower(lcase, domain, dlen + 1);
Curl_strntolower(lcookie, co->domain, clen + 1);
acceptable = psl_is_cookie_domain_acceptable(psl, lcase, lcookie);
Curl_psl_release(data);
}
else
infof(data, "libpsl problem, rejecting cookie for safety");
}
if(!acceptable) {
infof(data, "cookie '%s' dropped, domain '%s' must not "
"set cookies for '%s'", co->name, domain, co->domain);
return TRUE;
}
}
#else
(void)data;
(void)co;
(void)domain;
DEBUGF(infof(data, "NO PSL to check set-cookie '%s' for domain=%s in %s",
co->name, co->domain, domain));
#endif
return FALSE;
}
static bool replace_existing(struct Curl_easy *data,
struct Cookie *co,
struct CookieInfo *ci,
bool secure,
bool *replacep)
{
bool replace_old = FALSE;
struct Curl_llist_node *replace_n = NULL;
struct Curl_llist_node *n;
size_t myhash = cookiehash(co->domain);
for(n = Curl_llist_head(&ci->cookielist[myhash]); n; n = Curl_node_next(n)) {
struct Cookie *clist = Curl_node_elem(n);
if(!strcmp(clist->name, co->name)) {
bool matching_domains = FALSE;
if(clist->domain && co->domain) {
if(curl_strequal(clist->domain, co->domain))
matching_domains = TRUE;
}
else if(!clist->domain && !co->domain)
matching_domains = TRUE;
if(matching_domains &&
clist->path && co->path &&
clist->secure && !co->secure && !secure) {
size_t cllen;
const char *sep = NULL;
DEBUGASSERT(clist->path[0]);
if(clist->path[0])
sep = strchr(clist->path + 1, '/');
if(sep)
cllen = sep - clist->path;
else
cllen = strlen(clist->path);
if(curl_strnequal(clist->path, co->path, cllen)) {
infof(data, "cookie '%s' for domain '%s' dropped, would "
"overlay an existing cookie", co->name, co->domain);
return FALSE;
}
}
}
if(!replace_n && !strcmp(clist->name, co->name)) {
if(clist->domain && co->domain) {
if(curl_strequal(clist->domain, co->domain) &&
(clist->tailmatch == co->tailmatch))
replace_old = TRUE;
}
else if(!clist->domain && !co->domain)
replace_old = TRUE;
if(replace_old) {
if(clist->path && co->path &&
!curl_strequal(clist->path, co->path))
replace_old = FALSE;
else if(!clist->path != !co->path)
replace_old = FALSE;
}
if(replace_old && !co->livecookie && clist->livecookie) {
return FALSE;
}
if(replace_old)
replace_n = n;
}
}
if(replace_n) {
struct Cookie *repl = Curl_node_elem(replace_n);
co->creationtime = repl->creationtime;
Curl_node_remove(replace_n);
freecookie(repl, TRUE);
}
*replacep = replace_old;
return TRUE;
}
CURLcode
Curl_cookie_add(struct Curl_easy *data,
struct CookieInfo *ci,
bool httpheader,
bool noexpire,
const char *lineptr,
const char *domain,
const char *path,
bool secure)
{
struct Cookie comem;
struct Cookie *co;
size_t myhash;
CURLcode result;
bool replaces = FALSE;
bool okay;
DEBUGASSERT(data);
DEBUGASSERT(MAX_SET_COOKIE_AMOUNT <= 255);
if(data->req.setcookies >= MAX_SET_COOKIE_AMOUNT)
return CURLE_OK;
co = &comem;
memset(co, 0, sizeof(comem));
if(httpheader)
result = parse_cookie_header(data, co, ci, &okay,
lineptr, domain, path, secure);
else
result = parse_netscape(co, ci, &okay, lineptr, secure);
if(result || !okay)
goto fail;
if(co->prefix_secure && !co->secure)
goto fail;
if(co->prefix_host) {
if(co->secure && co->path && !strcmp(co->path, "/") && !co->tailmatch)
;
else
goto fail;
}
if(!ci->running &&
ci->newsession &&
!co->expires)
goto fail;
co->livecookie = ci->running;
co->creationtime = ++ci->lastct;
if(!noexpire)
remove_expired(ci);
if(is_public_suffix(data, co, domain))
goto fail;
if(!replace_existing(data, co, ci, secure, &replaces))
goto fail;
co = Curl_memdup(&comem, sizeof(comem));
if(!co) {
co = &comem;
result = CURLE_OUT_OF_MEMORY;
goto fail;
}
myhash = cookiehash(co->domain);
Curl_llist_append(&ci->cookielist[myhash], co, &co->node);
if(ci->running)
infof(data, "%s cookie %s=\"%s\" for domain %s, path %s, "
"expire %" FMT_OFF_T,
replaces ? "Replaced" : "Added", co->name, co->value,
co->domain, co->path, co->expires);
if(!replaces)
ci->numcookies++;
if(co->expires && (co->expires < ci->next_expiration))
ci->next_expiration = co->expires;
if(httpheader)
data->req.setcookies++;
return result;
fail:
freecookie(co, FALSE);
return result;
}
struct CookieInfo *Curl_cookie_init(void)
{
int i;
struct CookieInfo *ci = curlx_calloc(1, sizeof(struct CookieInfo));
if(!ci)
return NULL;
for(i = 0; i < COOKIE_HASH_SIZE; i++)
Curl_llist_init(&ci->cookielist[i], NULL);
ci->next_expiration = CURL_OFF_T_MAX;
return ci;
}
static CURLcode cookie_load(struct Curl_easy *data, const char *file,
struct CookieInfo *ci, bool newsession)
{
FILE *handle = NULL;
CURLcode result = CURLE_OK;
FILE *fp = NULL;
DEBUGASSERT(ci);
DEBUGASSERT(data);
DEBUGASSERT(file);
ci->newsession = newsession;
ci->running = FALSE;
if(file && *file) {
if(!strcmp(file, "-"))
fp = stdin;
else {
fp = curlx_fopen(file, "rb");
if(!fp)
infof(data, "WARNING: failed to open cookie file \"%s\"", file);
else
handle = fp;
}
}
if(fp) {
struct dynbuf buf;
bool eof = FALSE;
curlx_dyn_init(&buf, MAX_COOKIE_LINE);
do {
result = Curl_get_line(&buf, fp, &eof);
if(!result) {
const char *lineptr = curlx_dyn_ptr(&buf);
bool headerline = FALSE;
if(checkprefix("Set-Cookie:", lineptr)) {
lineptr += 11;
headerline = TRUE;
curlx_str_passblanks(&lineptr);
}
result = Curl_cookie_add(data, ci, headerline, TRUE, lineptr, NULL,
NULL, TRUE);
}
} while(!result && !eof);
curlx_dyn_free(&buf);
remove_expired(ci);
if(handle)
curlx_fclose(handle);
}
data->state.cookie_engine = TRUE;
ci->running = TRUE;
return result;
}
CURLcode Curl_cookie_loadfiles(struct Curl_easy *data)
{
CURLcode result = CURLE_OK;
struct curl_slist *list = data->state.cookielist;
if(list) {
Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
if(!data->cookies)
data->cookies = Curl_cookie_init();
if(!data->cookies)
result = CURLE_OUT_OF_MEMORY;
else {
data->state.cookie_engine = TRUE;
while(list) {
result = cookie_load(data, list->data, data->cookies,
data->set.cookiesession);
if(result)
break;
list = list->next;
}
}
Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
}
return result;
}
static int cookie_sort(const void *p1, const void *p2)
{
const struct Cookie *c1 = *(const struct Cookie * const *)p1;
const struct Cookie *c2 = *(const struct Cookie * const *)p2;
size_t l1, l2;
l1 = c1->path ? strlen(c1->path) : 0;
l2 = c2->path ? strlen(c2->path) : 0;
if(l1 != l2)
return (l2 > l1) ? 1 : -1;
l1 = c1->domain ? strlen(c1->domain) : 0;
l2 = c2->domain ? strlen(c2->domain) : 0;
if(l1 != l2)
return (l2 > l1) ? 1 : -1;
l1 = c1->name ? strlen(c1->name) : 0;
l2 = c2->name ? strlen(c2->name) : 0;
if(l1 != l2)
return (l2 > l1) ? 1 : -1;
return (c2->creationtime > c1->creationtime) ? 1 : -1;
}
static int cookie_sort_ct(const void *p1, const void *p2)
{
const struct Cookie *c1 = *(const struct Cookie * const *)p1;
const struct Cookie *c2 = *(const struct Cookie * const *)p2;
return (c2->creationtime > c1->creationtime) ? 1 : -1;
}
bool Curl_secure_context(struct connectdata *conn, const char *host)
{
return conn->handler->protocol & (CURLPROTO_HTTPS | CURLPROTO_WSS) ||
curl_strequal("localhost", host) ||
!strcmp(host, "127.0.0.1") ||
!strcmp(host, "::1");
}
CURLcode Curl_cookie_getlist(struct Curl_easy *data,
struct connectdata *conn,
bool *okay,
const char *host,
struct Curl_llist *list)
{
size_t matches = 0;
const bool is_ip = Curl_host_is_ipnum(host);
const size_t myhash = cookiehash(host);
struct Curl_llist_node *n;
const bool secure = Curl_secure_context(conn, host);
struct CookieInfo *ci = data->cookies;
const char *path = data->state.up.path;
CURLcode result = CURLE_OK;
*okay = FALSE;
Curl_llist_init(list, NULL);
if(!ci || !Curl_llist_count(&ci->cookielist[myhash]))
return CURLE_OK;
remove_expired(ci);
for(n = Curl_llist_head(&ci->cookielist[myhash]); n; n = Curl_node_next(n)) {
struct Cookie *co = Curl_node_elem(n);
if(co->secure ? secure : TRUE) {
if(!co->domain ||
(co->tailmatch && !is_ip &&
cookie_tailmatch(co->domain, strlen(co->domain), host)) ||
((!co->tailmatch || is_ip) && curl_strequal(host, co->domain))) {
if(!co->path || pathmatch(co->path, path)) {
Curl_llist_append(list, co, &co->getnode);
matches++;
if(matches >= MAX_COOKIE_SEND_AMOUNT) {
infof(data, "Included max number of cookies (%zu) in request!",
matches);
break;
}
}
}
}
}
if(matches) {
struct Cookie **array;
size_t i;
array = curlx_malloc(sizeof(struct Cookie *) * matches);
if(!array) {
result = CURLE_OUT_OF_MEMORY;
goto fail;
}
n = Curl_llist_head(list);
for(i = 0; n; n = Curl_node_next(n))
array[i++] = Curl_node_elem(n);
qsort(array, matches, sizeof(struct Cookie *), cookie_sort);
Curl_llist_destroy(list, NULL);
for(i = 0; i < matches; i++)
Curl_llist_append(list, array[i], &array[i]->getnode);
curlx_free(array);
}
*okay = TRUE;
return CURLE_OK;
fail:
Curl_llist_destroy(list, NULL);
return result;
}
void Curl_cookie_clearall(struct CookieInfo *ci)
{
if(ci) {
unsigned int i;
for(i = 0; i < COOKIE_HASH_SIZE; i++) {
struct Curl_llist_node *n;
for(n = Curl_llist_head(&ci->cookielist[i]); n;) {
struct Cookie *c = Curl_node_elem(n);
struct Curl_llist_node *e = Curl_node_next(n);
Curl_node_remove(n);
freecookie(c, TRUE);
n = e;
}
}
ci->numcookies = 0;
}
}
void Curl_cookie_clearsess(struct CookieInfo *ci)
{
unsigned int i;
if(!ci)
return;
for(i = 0; i < COOKIE_HASH_SIZE; i++) {
struct Curl_llist_node *n = Curl_llist_head(&ci->cookielist[i]);
struct Curl_llist_node *e = NULL;
for(; n; n = e) {
struct Cookie *curr = Curl_node_elem(n);
e = Curl_node_next(n);
if(!curr->expires) {
Curl_node_remove(n);
freecookie(curr, TRUE);
ci->numcookies--;
}
}
}
}
void Curl_cookie_cleanup(struct CookieInfo *ci)
{
if(ci) {
Curl_cookie_clearall(ci);
curlx_free(ci);
}
}
static char *get_netscape_format(const struct Cookie *co)
{
return curl_maprintf(
"%s"
"%s%s\t"
"%s\t"
"%s\t"
"%s\t"
"%" FMT_OFF_T "\t"
"%s\t"
"%s",
co->httponly ? "#HttpOnly_" : "",
(co->tailmatch && co->domain && co->domain[0] != '.') ? "." : "",
co->domain ? co->domain : "unknown",
co->tailmatch ? "TRUE" : "FALSE",
co->path ? co->path : "/",
co->secure ? "TRUE" : "FALSE",
co->expires,
co->name,
co->value ? co->value : "");
}
static CURLcode cookie_output(struct Curl_easy *data,
struct CookieInfo *ci,
const char *filename)
{
FILE *out = NULL;
bool use_stdout = FALSE;
char *tempstore = NULL;
CURLcode error = CURLE_OK;
if(!ci)
return CURLE_OK;
remove_expired(ci);
if(!strcmp("-", filename)) {
out = stdout;
use_stdout = TRUE;
}
else {
error = Curl_fopen(data, filename, &out, &tempstore);
if(error)
goto error;
}
fputs("# Netscape HTTP Cookie File\n"
"# https://curl.se/docs/http-cookies.html\n"
"# This file was generated by libcurl! Edit at your own risk.\n\n",
out);
if(ci->numcookies) {
unsigned int i;
size_t nvalid = 0;
struct Cookie **array;
struct Curl_llist_node *n;
array = curlx_calloc(1, sizeof(struct Cookie *) * ci->numcookies);
if(!array) {
error = CURLE_OUT_OF_MEMORY;
goto error;
}
for(i = 0; i < COOKIE_HASH_SIZE; i++) {
for(n = Curl_llist_head(&ci->cookielist[i]); n; n = Curl_node_next(n)) {
struct Cookie *co = Curl_node_elem(n);
if(!co->domain)
continue;
array[nvalid++] = co;
}
}
qsort(array, nvalid, sizeof(struct Cookie *), cookie_sort_ct);
for(i = 0; i < nvalid; i++) {
char *format_ptr = get_netscape_format(array[i]);
if(!format_ptr) {
curlx_free(array);
error = CURLE_OUT_OF_MEMORY;
goto error;
}
curl_mfprintf(out, "%s\n", format_ptr);
curlx_free(format_ptr);
}
curlx_free(array);
}
if(!use_stdout) {
curlx_fclose(out);
out = NULL;
if(tempstore && curlx_rename(tempstore, filename)) {
error = CURLE_WRITE_ERROR;
goto error;
}
}
curlx_free(tempstore);
return CURLE_OK;
error:
if(out && !use_stdout)
curlx_fclose(out);
if(tempstore) {
unlink(tempstore);
curlx_free(tempstore);
}
return error;
}
static struct curl_slist *cookie_list(struct Curl_easy *data)
{
struct curl_slist *list = NULL;
struct curl_slist *beg;
unsigned int i;
struct Curl_llist_node *n;
if(!data->cookies || (data->cookies->numcookies == 0))
return NULL;
remove_expired(data->cookies);
for(i = 0; i < COOKIE_HASH_SIZE; i++) {
for(n = Curl_llist_head(&data->cookies->cookielist[i]); n;
n = Curl_node_next(n)) {
struct Cookie *c = Curl_node_elem(n);
char *line;
if(!c->domain)
continue;
line = get_netscape_format(c);
if(!line) {
curl_slist_free_all(list);
return NULL;
}
beg = Curl_slist_append_nodup(list, line);
if(!beg) {
curlx_free(line);
curl_slist_free_all(list);
return NULL;
}
list = beg;
}
}
return list;
}
struct curl_slist *Curl_cookie_list(struct Curl_easy *data)
{
struct curl_slist *list;
Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
list = cookie_list(data);
Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
return list;
}
void Curl_flush_cookies(struct Curl_easy *data, bool cleanup)
{
Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
if(data->cookies) {
if(data->set.str[STRING_COOKIEJAR] && data->cookies->running) {
CURLcode result = cookie_output(data, data->cookies,
data->set.str[STRING_COOKIEJAR]);
if(result)
infof(data, "WARNING: failed to save cookies in %s: %s",
data->set.str[STRING_COOKIEJAR], curl_easy_strerror(result));
}
if(cleanup && (!data->share || (data->cookies != data->share->cookies))) {
Curl_cookie_cleanup(data->cookies);
data->cookies = NULL;
}
}
Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
}
void Curl_cookie_run(struct Curl_easy *data)
{
Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
if(data->cookies)
data->cookies->running = TRUE;
Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
}
#endif