/***************************************************************************1* _ _ ____ _2* Project ___| | | | _ \| |3* / __| | | | |_) | |4* | (__| |_| | _ <| |___5* \___|\___/|_| \_\_____|6*7* Copyright (C) Daniel Stenberg, <[email protected]>, et al.8*9* This software is licensed as described in the file COPYING, which10* you should have received as part of this distribution. The terms11* are also available at https://curl.se/docs/copyright.html.12*13* You may opt to use, copy, modify, merge, publish, distribute and/or sell14* copies of the Software, and permit persons to whom the Software is15* furnished to do so, under the terms of the COPYING file.16*17* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY18* KIND, either express or implied.19*20* SPDX-License-Identifier: curl21*22***************************************************************************/2324/***252627RECEIVING COOKIE INFORMATION28============================2930Curl_cookie_init()3132Inits a cookie struct to store data in a local file. This is always33called before any cookies are set.3435Curl_cookie_add()3637Adds a cookie to the in-memory cookie jar.383940SENDING COOKIE INFORMATION41==========================4243Curl_cookie_getlist()4445For a given host and path, return a linked list of cookies that46the client should send to the server if used now. The secure47boolean informs the cookie if a secure connection is achieved or48not.4950It shall only return cookies that have not expired.5152Example set of cookies:5354Set-cookie: PRODUCTINFO=webxpress; domain=.fidelity.com; path=/; secure55Set-cookie: PERSONALIZE=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;56domain=.fidelity.com; path=/ftgw; secure57Set-cookie: FidHist=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;58domain=.fidelity.com; path=/; secure59Set-cookie: FidOrder=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;60domain=.fidelity.com; path=/; secure61Set-cookie: DisPend=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;62domain=.fidelity.com; path=/; secure63Set-cookie: FidDis=none;expires=Monday, 13-Jun-1988 03:04:55 GMT;64domain=.fidelity.com; path=/; secure65Set-cookie:66Session_Key@6791a9e0-901a-11d0-a1c8-9b012c88aa77=none;expires=Monday,6713-Jun-1988 03:04:55 GMT; domain=.fidelity.com; path=/; secure68****/697071#include "curl_setup.h"7273#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES)7475#include "urldata.h"76#include "cookie.h"77#include "psl.h"78#include "sendf.h"79#include "slist.h"80#include "share.h"81#include "strcase.h"82#include "curl_fopen.h"83#include "curl_get_line.h"84#include "curl_memrchr.h"85#include "parsedate.h"86#include "rename.h"87#include "strdup.h"88#include "llist.h"89#include "curlx/strparse.h"9091/* The last 2 #include files should be in this order */92#include "curl_memory.h"93#include "memdebug.h"9495static void strstore(char **str, const char *newstr, size_t len);9697/* number of seconds in 400 days */98#define COOKIES_MAXAGE (400*24*3600)99100/* Make sure cookies never expire further away in time than 400 days into the101future. (from RFC6265bis draft-19)102103For the sake of easier testing, align the capped time to an even 60 second104boundary.105*/106static void cap_expires(time_t now, struct Cookie *co)107{108if(co->expires && (TIME_T_MAX - COOKIES_MAXAGE - 30) > now) {109timediff_t cap = now + COOKIES_MAXAGE;110if(co->expires > cap) {111cap += 30;112co->expires = (cap/60)*60;113}114}115}116117static void freecookie(struct Cookie *co)118{119free(co->domain);120free(co->path);121free(co->spath);122free(co->name);123free(co->value);124free(co);125}126127static bool cookie_tailmatch(const char *cookie_domain,128size_t cookie_domain_len,129const char *hostname)130{131size_t hostname_len = strlen(hostname);132133if(hostname_len < cookie_domain_len)134return FALSE;135136if(!curl_strnequal(cookie_domain,137hostname + hostname_len-cookie_domain_len,138cookie_domain_len))139return FALSE;140141/*142* A lead char of cookie_domain is not '.'.143* RFC6265 4.1.2.3. The Domain Attribute says:144* For example, if the value of the Domain attribute is145* "example.com", the user agent will include the cookie in the Cookie146* header when making HTTP requests to example.com, www.example.com, and147* www.corp.example.com.148*/149if(hostname_len == cookie_domain_len)150return TRUE;151if('.' == *(hostname + hostname_len - cookie_domain_len - 1))152return TRUE;153return FALSE;154}155156/*157* matching cookie path and URL path158* RFC6265 5.1.4 Paths and Path-Match159*/160static bool pathmatch(const char *cookie_path, const char *uri_path)161{162size_t cookie_path_len;163size_t uri_path_len;164bool ret = FALSE;165166/* cookie_path must not have last '/' separator. ex: /sample */167cookie_path_len = strlen(cookie_path);168if(cookie_path_len == 1) {169/* cookie_path must be '/' */170return TRUE;171}172173/* #-fragments are already cut off! */174if(strlen(uri_path) == 0 || uri_path[0] != '/')175uri_path = "/";176177/*178* here, RFC6265 5.1.4 says179* 4. Output the characters of the uri-path from the first character up180* to, but not including, the right-most %x2F ("/").181* but URL path /hoge?fuga=xxx means /hoge/index.cgi?fuga=xxx in some site182* without redirect.183* Ignore this algorithm because /hoge is uri path for this case184* (uri path is not /).185*/186187uri_path_len = strlen(uri_path);188189if(uri_path_len < cookie_path_len)190goto pathmatched;191192/* not using checkprefix() because matching should be case-sensitive */193if(strncmp(cookie_path, uri_path, cookie_path_len))194goto pathmatched;195196/* The cookie-path and the uri-path are identical. */197if(cookie_path_len == uri_path_len) {198ret = TRUE;199goto pathmatched;200}201202/* here, cookie_path_len < uri_path_len */203if(uri_path[cookie_path_len] == '/') {204ret = TRUE;205goto pathmatched;206}207208pathmatched:209return ret;210}211212/*213* Return the top-level domain, for optimal hashing.214*/215static const char *get_top_domain(const char * const domain, size_t *outlen)216{217size_t len = 0;218const char *first = NULL, *last;219220if(domain) {221len = strlen(domain);222last = memrchr(domain, '.', len);223if(last) {224first = memrchr(domain, '.', (last - domain));225if(first)226len -= (++first - domain);227}228}229230if(outlen)231*outlen = len;232233return first ? first : domain;234}235236/* Avoid C1001, an "internal error" with MSVC14 */237#if defined(_MSC_VER) && (_MSC_VER == 1900)238#pragma optimize("", off)239#endif240241/*242* A case-insensitive hash for the cookie domains.243*/244static size_t cookie_hash_domain(const char *domain, const size_t len)245{246const char *end = domain + len;247size_t h = 5381;248249while(domain < end) {250size_t j = (size_t)Curl_raw_toupper(*domain++);251h += h << 5;252h ^= j;253}254255return (h % COOKIE_HASH_SIZE);256}257258#if defined(_MSC_VER) && (_MSC_VER == 1900)259#pragma optimize("", on)260#endif261262/*263* Hash this domain.264*/265static size_t cookiehash(const char * const domain)266{267const char *top;268size_t len;269270if(!domain || Curl_host_is_ipnum(domain))271return 0;272273top = get_top_domain(domain, &len);274return cookie_hash_domain(top, len);275}276277/*278* cookie path sanitize279*/280static char *sanitize_cookie_path(const char *cookie_path)281{282size_t len = strlen(cookie_path);283284/* some sites send path attribute within '"'. */285if(cookie_path[0] == '\"') {286cookie_path++;287len--;288}289if(len && (cookie_path[len - 1] == '\"'))290len--;291292/* RFC6265 5.2.4 The Path Attribute */293if(cookie_path[0] != '/')294/* Let cookie-path be the default-path. */295return strdup("/");296297/* remove trailing slash when path is non-empty */298/* convert /hoge/ to /hoge */299if(len > 1 && cookie_path[len - 1] == '/')300len--;301302return Curl_memdup0(cookie_path, len);303}304305/*306* Load cookies from all given cookie files (CURLOPT_COOKIEFILE).307*308* NOTE: OOM or cookie parsing failures are ignored.309*/310void Curl_cookie_loadfiles(struct Curl_easy *data)311{312struct curl_slist *list = data->state.cookielist;313if(list) {314Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);315while(list) {316struct CookieInfo *ci =317Curl_cookie_init(data, list->data, data->cookies,318data->set.cookiesession);319if(!ci)320/*321* Failure may be due to OOM or a bad cookie; both are ignored322* but only the first should be323*/324infof(data, "ignoring failed cookie_init for %s", list->data);325else326data->cookies = ci;327list = list->next;328}329Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);330}331}332333/*334* strstore335*336* A thin wrapper around strdup which ensures that any memory allocated at337* *str will be freed before the string allocated by strdup is stored there.338* The intended usecase is repeated assignments to the same variable during339* parsing in a last-wins scenario. The caller is responsible for checking340* for OOM errors.341*/342static void strstore(char **str, const char *newstr, size_t len)343{344DEBUGASSERT(str);345free(*str);346if(!len) {347len++;348newstr = "";349}350*str = Curl_memdup0(newstr, len);351}352353/*354* remove_expired355*356* Remove expired cookies from the hash by inspecting the expires timestamp on357* each cookie in the hash, freeing and deleting any where the timestamp is in358* the past. If the cookiejar has recorded the next timestamp at which one or359* more cookies expire, then processing will exit early in case this timestamp360* is in the future.361*/362static void remove_expired(struct CookieInfo *ci)363{364struct Cookie *co;365curl_off_t now = (curl_off_t)time(NULL);366unsigned int i;367368/*369* If the earliest expiration timestamp in the jar is in the future we can370* skip scanning the whole jar and instead exit early as there will not be371* any cookies to evict. If we need to evict however, reset the372* next_expiration counter in order to track the next one. In case the373* recorded first expiration is the max offset, then perform the safe374* fallback of checking all cookies.375*/376if(now < ci->next_expiration &&377ci->next_expiration != CURL_OFF_T_MAX)378return;379else380ci->next_expiration = CURL_OFF_T_MAX;381382for(i = 0; i < COOKIE_HASH_SIZE; i++) {383struct Curl_llist_node *n;384struct Curl_llist_node *e = NULL;385386for(n = Curl_llist_head(&ci->cookielist[i]); n; n = e) {387co = Curl_node_elem(n);388e = Curl_node_next(n);389if(co->expires) {390if(co->expires < now) {391Curl_node_remove(n);392freecookie(co);393ci->numcookies--;394}395else if(co->expires < ci->next_expiration)396/*397* If this cookie has an expiration timestamp earlier than what we398* have seen so far then record it for the next round of expirations.399*/400ci->next_expiration = co->expires;401}402}403}404}405406#ifndef USE_LIBPSL407/* Make sure domain contains a dot or is localhost. */408static bool bad_domain(const char *domain, size_t len)409{410if((len == 9) && curl_strnequal(domain, "localhost", 9))411return FALSE;412else {413/* there must be a dot present, but that dot must not be a trailing dot */414char *dot = memchr(domain, '.', len);415if(dot) {416size_t i = dot - domain;417if((len - i) > 1)418/* the dot is not the last byte */419return FALSE;420}421}422return TRUE;423}424#endif425426/*427RFC 6265 section 4.1.1 says a server should accept this range:428429cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E430431But Firefox and Chrome as of June 2022 accept space, comma and double-quotes432fine. The prime reason for filtering out control bytes is that some HTTP433servers return 400 for requests that contain such.434*/435static bool invalid_octets(const char *ptr)436{437const unsigned char *p = (const unsigned char *)ptr;438/* Reject all bytes \x01 - \x1f (*except* \x09, TAB) + \x7f */439while(*p) {440if(((*p != 9) && (*p < 0x20)) || (*p == 0x7f))441return TRUE;442p++;443}444return FALSE;445}446447#define CERR_OK 0448#define CERR_TOO_LONG 1 /* input line too long */449#define CERR_TAB 2 /* in a wrong place */450#define CERR_TOO_BIG 3 /* name/value too large */451#define CERR_BAD 4 /* deemed incorrect */452#define CERR_NO_SEP 5 /* semicolon problem */453#define CERR_NO_NAME_VALUE 6 /* name or value problem */454#define CERR_INVALID_OCTET 7 /* bad content */455#define CERR_BAD_SECURE 8 /* secure in a bad place */456#define CERR_OUT_OF_MEMORY 9457#define CERR_NO_TAILMATCH 10458#define CERR_COMMENT 11 /* a commented line */459#define CERR_RANGE 12 /* expire range problem */460#define CERR_FIELDS 13 /* incomplete netscape line */461#ifdef USE_LIBPSL462#define CERR_PSL 14 /* a public suffix */463#endif464#define CERR_LIVE_WINS 15465466/* The maximum length we accept a date string for the 'expire' keyword. The467standard date formats are within the 30 bytes range. This adds an extra468margin just to make sure it realistically works with what is used out469there.470*/471#define MAX_DATE_LENGTH 80472473static int474parse_cookie_header(struct Curl_easy *data,475struct Cookie *co,476struct CookieInfo *ci,477const char *ptr,478const char *domain, /* default domain */479const char *path, /* full path used when this cookie is480set, used to get default path for481the cookie unless set */482bool secure) /* TRUE if connection is over secure483origin */484{485/* This line was read off an HTTP-header */486time_t now;487size_t linelength = strlen(ptr);488if(linelength > MAX_COOKIE_LINE)489/* discard overly long lines at once */490return CERR_TOO_LONG;491492now = time(NULL);493do {494struct Curl_str name;495struct Curl_str val;496497/* we have a <name>=<value> pair or a stand-alone word here */498if(!curlx_str_cspn(&ptr, &name, ";\t\r\n=")) {499bool done = FALSE;500bool sep = FALSE;501curlx_str_trimblanks(&name);502503if(!curlx_str_single(&ptr, '=')) {504sep = TRUE; /* a '=' was used */505if(!curlx_str_cspn(&ptr, &val, ";\r\n")) {506curlx_str_trimblanks(&val);507508/* Reject cookies with a TAB inside the value */509if(memchr(curlx_str(&val), '\t', curlx_strlen(&val))) {510infof(data, "cookie contains TAB, dropping");511return CERR_TAB;512}513}514}515else {516curlx_str_init(&val);517}518519/*520* Check for too long individual name or contents, or too long521* combination of name + contents. Chrome and Firefox support 4095 or522* 4096 bytes combo523*/524if(curlx_strlen(&name) >= (MAX_NAME-1) ||525curlx_strlen(&val) >= (MAX_NAME-1) ||526((curlx_strlen(&name) + curlx_strlen(&val)) > MAX_NAME)) {527infof(data, "oversized cookie dropped, name/val %zu + %zu bytes",528curlx_strlen(&name), curlx_strlen(&val));529return CERR_TOO_BIG;530}531532/*533* Check if we have a reserved prefix set before anything else, as we534* otherwise have to test for the prefix in both the cookie name and535* "the rest". Prefixes must start with '__' and end with a '-', so536* only test for names where that can possibly be true.537*/538if(!strncmp("__Secure-", curlx_str(&name), 9))539co->prefix_secure = TRUE;540else if(!strncmp("__Host-", curlx_str(&name), 7))541co->prefix_host = TRUE;542543/*544* Use strstore() below to properly deal with received cookie545* headers that have the same string property set more than once,546* and then we use the last one.547*/548549if(!co->name) {550/* The very first name/value pair is the actual cookie name */551if(!sep)552/* Bad name/value pair. */553return CERR_NO_SEP;554555strstore(&co->name, curlx_str(&name), curlx_strlen(&name));556strstore(&co->value, curlx_str(&val), curlx_strlen(&val));557done = TRUE;558if(!co->name || !co->value)559return CERR_NO_NAME_VALUE;560561if(invalid_octets(co->value) || invalid_octets(co->name)) {562infof(data, "invalid octets in name/value, cookie dropped");563return CERR_INVALID_OCTET;564}565}566else if(!curlx_strlen(&val)) {567/*568* this was a "<name>=" with no content, and we must allow569* 'secure' and 'httponly' specified this weirdly570*/571done = TRUE;572/*573* secure cookies are only allowed to be set when the connection is574* using a secure protocol, or when the cookie is being set by575* reading from file576*/577if(curlx_str_casecompare(&name, "secure")) {578if(secure || !ci->running) {579co->secure = TRUE;580}581else {582return CERR_BAD_SECURE;583}584}585else if(curlx_str_casecompare(&name, "httponly"))586co->httponly = TRUE;587else if(sep)588/* there was a '=' so we are not done parsing this field */589done = FALSE;590}591if(done)592;593else if(curlx_str_casecompare(&name, "path")) {594strstore(&co->path, curlx_str(&val), curlx_strlen(&val));595if(!co->path)596return CERR_OUT_OF_MEMORY;597free(co->spath); /* if this is set again */598co->spath = sanitize_cookie_path(co->path);599if(!co->spath)600return CERR_OUT_OF_MEMORY;601}602else if(curlx_str_casecompare(&name, "domain") && curlx_strlen(&val)) {603bool is_ip;604const char *v = curlx_str(&val);605/*606* Now, we make sure that our host is within the given domain, or607* the given domain is not valid and thus cannot be set.608*/609610if('.' == *v)611curlx_str_nudge(&val, 1);612613#ifndef USE_LIBPSL614/*615* Without PSL we do not know when the incoming cookie is set on a616* TLD or otherwise "protected" suffix. To reduce risk, we require a617* dot OR the exact hostname being "localhost".618*/619if(bad_domain(curlx_str(&val), curlx_strlen(&val)))620domain = ":";621#endif622623is_ip = Curl_host_is_ipnum(domain ? domain : curlx_str(&val));624625if(!domain626|| (is_ip && !strncmp(curlx_str(&val), domain,627curlx_strlen(&val)) &&628(curlx_strlen(&val) == strlen(domain)))629|| (!is_ip && cookie_tailmatch(curlx_str(&val),630curlx_strlen(&val), domain))) {631strstore(&co->domain, curlx_str(&val), curlx_strlen(&val));632if(!co->domain)633return CERR_OUT_OF_MEMORY;634635if(!is_ip)636co->tailmatch = TRUE; /* we always do that if the domain name was637given */638}639else {640/*641* We did not get a tailmatch and then the attempted set domain is642* not a domain to which the current host belongs. Mark as bad.643*/644infof(data, "skipped cookie with bad tailmatch domain: %s",645curlx_str(&val));646return CERR_NO_TAILMATCH;647}648}649else if(curlx_str_casecompare(&name, "version")) {650/* just ignore */651}652else if(curlx_str_casecompare(&name, "max-age") && curlx_strlen(&val)) {653/*654* Defined in RFC2109:655*656* Optional. The Max-Age attribute defines the lifetime of the657* cookie, in seconds. The delta-seconds value is a decimal non-658* negative integer. After delta-seconds seconds elapse, the659* client should discard the cookie. A value of zero means the660* cookie should be discarded immediately.661*/662int rc;663const char *maxage = curlx_str(&val);664if(*maxage == '\"')665maxage++;666rc = curlx_str_number(&maxage, &co->expires, CURL_OFF_T_MAX);667switch(rc) {668case STRE_OVERFLOW:669/* overflow, used max value */670co->expires = CURL_OFF_T_MAX;671break;672default:673/* negative or otherwise bad, expire */674co->expires = 1;675break;676case STRE_OK:677if(!co->expires)678co->expires = 1; /* expire now */679else if(CURL_OFF_T_MAX - now < co->expires)680/* would overflow */681co->expires = CURL_OFF_T_MAX;682else683co->expires += now;684break;685}686cap_expires(now, co);687}688else if(curlx_str_casecompare(&name, "expires") && curlx_strlen(&val)) {689if(!co->expires && (curlx_strlen(&val) < MAX_DATE_LENGTH)) {690/*691* Let max-age have priority.692*693* If the date cannot get parsed for whatever reason, the cookie694* will be treated as a session cookie695*/696char dbuf[MAX_DATE_LENGTH + 1];697time_t date = 0;698memcpy(dbuf, curlx_str(&val), curlx_strlen(&val));699dbuf[curlx_strlen(&val)] = 0;700if(!Curl_getdate_capped(dbuf, &date)) {701if(!date)702date++;703co->expires = (curl_off_t)date;704}705else706co->expires = 0;707cap_expires(now, co);708}709}710711/*712* Else, this is the second (or more) name we do not know about!713*/714}715716if(curlx_str_single(&ptr, ';'))717break;718} while(1);719720if(!co->domain && domain) {721/* no domain was given in the header line, set the default */722co->domain = strdup(domain);723if(!co->domain)724return CERR_OUT_OF_MEMORY;725}726727if(!co->path && path) {728/*729* No path was given in the header line, set the default.730*/731const char *endslash = strrchr(path, '/');732if(endslash) {733size_t pathlen = (endslash - path + 1); /* include end slash */734co->path = Curl_memdup0(path, pathlen);735if(co->path) {736co->spath = sanitize_cookie_path(co->path);737if(!co->spath)738return CERR_OUT_OF_MEMORY;739}740else741return CERR_OUT_OF_MEMORY;742}743}744745/*746* If we did not get a cookie name, or a bad one, the this is an illegal747* line so bail out.748*/749if(!co->name)750return CERR_BAD;751752return CERR_OK;753}754755static int756parse_netscape(struct Cookie *co,757struct CookieInfo *ci,758const char *lineptr,759bool secure) /* TRUE if connection is over secure760origin */761{762/*763* This line is NOT an HTTP header style line, we do offer support for764* reading the odd netscape cookies-file format here765*/766const char *ptr, *next;767int fields;768size_t len;769770/*771* In 2008, Internet Explorer introduced HTTP-only cookies to prevent XSS772* attacks. Cookies marked httpOnly are not accessible to JavaScript. In773* Firefox's cookie files, they are prefixed #HttpOnly_ and the rest774* remains as usual, so we skip 10 characters of the line.775*/776if(strncmp(lineptr, "#HttpOnly_", 10) == 0) {777lineptr += 10;778co->httponly = TRUE;779}780781if(lineptr[0]=='#')782/* do not even try the comments */783return CERR_COMMENT;784785/*786* Now loop through the fields and init the struct we already have787* allocated788*/789fields = 0;790for(next = lineptr; next; fields++) {791ptr = next;792len = strcspn(ptr, "\t\r\n");793next = (ptr[len] == '\t' ? &ptr[len + 1] : NULL);794switch(fields) {795case 0:796if(ptr[0]=='.') { /* skip preceding dots */797ptr++;798len--;799}800co->domain = Curl_memdup0(ptr, len);801if(!co->domain)802return CERR_OUT_OF_MEMORY;803break;804case 1:805/*806* flag: A TRUE/FALSE value indicating if all machines within a given807* domain can access the variable. Set TRUE when the cookie says808* .example.com and to false when the domain is complete www.example.com809*/810co->tailmatch = !!curl_strnequal(ptr, "TRUE", len);811break;812case 2:813/* The file format allows the path field to remain not filled in */814if(strncmp("TRUE", ptr, len) && strncmp("FALSE", ptr, len)) {815/* only if the path does not look like a boolean option! */816co->path = Curl_memdup0(ptr, len);817if(!co->path)818return CERR_OUT_OF_MEMORY;819else {820co->spath = sanitize_cookie_path(co->path);821if(!co->spath)822return CERR_OUT_OF_MEMORY;823}824break;825}826/* this does not look like a path, make one up! */827co->path = strdup("/");828if(!co->path)829return CERR_OUT_OF_MEMORY;830co->spath = strdup("/");831if(!co->spath)832return CERR_OUT_OF_MEMORY;833fields++; /* add a field and fall down to secure */834FALLTHROUGH();835case 3:836co->secure = FALSE;837if(curl_strnequal(ptr, "TRUE", len)) {838if(secure || ci->running)839co->secure = TRUE;840else841return CERR_BAD_SECURE;842}843break;844case 4:845if(curlx_str_number(&ptr, &co->expires, CURL_OFF_T_MAX))846return CERR_RANGE;847break;848case 5:849co->name = Curl_memdup0(ptr, len);850if(!co->name)851return CERR_OUT_OF_MEMORY;852else {853/* For Netscape file format cookies we check prefix on the name */854if(curl_strnequal("__Secure-", co->name, 9))855co->prefix_secure = TRUE;856else if(curl_strnequal("__Host-", co->name, 7))857co->prefix_host = TRUE;858}859break;860case 6:861co->value = Curl_memdup0(ptr, len);862if(!co->value)863return CERR_OUT_OF_MEMORY;864break;865}866}867if(fields == 6) {868/* we got a cookie with blank contents, fix it */869co->value = strdup("");870if(!co->value)871return CERR_OUT_OF_MEMORY;872else873fields++;874}875876if(fields != 7)877/* we did not find the sufficient number of fields */878return CERR_FIELDS;879880return CERR_OK;881}882883static int884is_public_suffix(struct Curl_easy *data,885struct Cookie *co,886const char *domain)887{888#ifdef USE_LIBPSL889/*890* Check if the domain is a Public Suffix and if yes, ignore the cookie. We891* must also check that the data handle is not NULL since the psl code will892* dereference it.893*/894DEBUGF(infof(data, "PSL check set-cookie '%s' for domain=%s in %s",895co->name, co->domain, domain));896if(data && (domain && co->domain && !Curl_host_is_ipnum(co->domain))) {897bool acceptable = FALSE;898char lcase[256];899char lcookie[256];900size_t dlen = strlen(domain);901size_t clen = strlen(co->domain);902if((dlen < sizeof(lcase)) && (clen < sizeof(lcookie))) {903const psl_ctx_t *psl = Curl_psl_use(data);904if(psl) {905/* the PSL check requires lowercase domain name and pattern */906Curl_strntolower(lcase, domain, dlen + 1);907Curl_strntolower(lcookie, co->domain, clen + 1);908acceptable = psl_is_cookie_domain_acceptable(psl, lcase, lcookie);909Curl_psl_release(data);910}911else912infof(data, "libpsl problem, rejecting cookie for safety");913}914915if(!acceptable) {916infof(data, "cookie '%s' dropped, domain '%s' must not "917"set cookies for '%s'", co->name, domain, co->domain);918return CERR_PSL;919}920}921#else922(void)data;923(void)co;924(void)domain;925DEBUGF(infof(data, "NO PSL to check set-cookie '%s' for domain=%s in %s",926co->name, co->domain, domain));927#endif928return CERR_OK;929}930931static int932replace_existing(struct Curl_easy *data,933struct Cookie *co,934struct CookieInfo *ci,935bool secure,936bool *replacep)937{938bool replace_old = FALSE;939struct Curl_llist_node *replace_n = NULL;940struct Curl_llist_node *n;941size_t myhash = cookiehash(co->domain);942for(n = Curl_llist_head(&ci->cookielist[myhash]); n; n = Curl_node_next(n)) {943struct Cookie *clist = Curl_node_elem(n);944if(!strcmp(clist->name, co->name)) {945/* the names are identical */946bool matching_domains = FALSE;947948if(clist->domain && co->domain) {949if(curl_strequal(clist->domain, co->domain))950/* The domains are identical */951matching_domains = TRUE;952}953else if(!clist->domain && !co->domain)954matching_domains = TRUE;955956if(matching_domains && /* the domains were identical */957clist->spath && co->spath && /* both have paths */958clist->secure && !co->secure && !secure) {959size_t cllen;960const char *sep = NULL;961962/*963* A non-secure cookie may not overlay an existing secure cookie.964* For an existing cookie "a" with path "/login", refuse a new965* cookie "a" with for example path "/login/en", while the path966* "/loginhelper" is ok.967*/968969DEBUGASSERT(clist->spath[0]);970if(clist->spath[0])971sep = strchr(clist->spath + 1, '/');972if(sep)973cllen = sep - clist->spath;974else975cllen = strlen(clist->spath);976977if(curl_strnequal(clist->spath, co->spath, cllen)) {978infof(data, "cookie '%s' for domain '%s' dropped, would "979"overlay an existing cookie", co->name, co->domain);980return CERR_BAD_SECURE;981}982}983}984985if(!replace_n && !strcmp(clist->name, co->name)) {986/* the names are identical */987988if(clist->domain && co->domain) {989if(curl_strequal(clist->domain, co->domain) &&990(clist->tailmatch == co->tailmatch))991/* The domains are identical */992replace_old = TRUE;993}994else if(!clist->domain && !co->domain)995replace_old = TRUE;996997if(replace_old) {998/* the domains were identical */9991000if(clist->spath && co->spath &&1001!curl_strequal(clist->spath, co->spath))1002replace_old = FALSE;1003else if(!clist->spath != !co->spath)1004replace_old = FALSE;1005}10061007if(replace_old && !co->livecookie && clist->livecookie) {1008/*1009* Both cookies matched fine, except that the already present cookie1010* is "live", which means it was set from a header, while the new one1011* was read from a file and thus is not "live". "live" cookies are1012* preferred so the new cookie is freed.1013*/1014return CERR_LIVE_WINS;1015}1016if(replace_old)1017replace_n = n;1018}1019}1020if(replace_n) {1021struct Cookie *repl = Curl_node_elem(replace_n);10221023/* when replacing, creationtime is kept from old */1024co->creationtime = repl->creationtime;10251026/* unlink the old */1027Curl_node_remove(replace_n);10281029/* free the old cookie */1030freecookie(repl);1031}1032*replacep = replace_old;1033return CERR_OK;1034}10351036/*1037* Curl_cookie_add1038*1039* Add a single cookie line to the cookie keeping object. Be aware that1040* sometimes we get an IP-only hostname, and that might also be a numerical1041* IPv6 address.1042*1043* Returns NULL on out of memory or invalid cookie. This is suboptimal,1044* as they should be treated separately.1045*/1046struct Cookie *1047Curl_cookie_add(struct Curl_easy *data,1048struct CookieInfo *ci,1049bool httpheader, /* TRUE if HTTP header-style line */1050bool noexpire, /* if TRUE, skip remove_expired() */1051const char *lineptr, /* first character of the line */1052const char *domain, /* default domain */1053const char *path, /* full path used when this cookie is set,1054used to get default path for the cookie1055unless set */1056bool secure) /* TRUE if connection is over secure origin */1057{1058struct Cookie *co;1059size_t myhash;1060int rc;1061bool replaces = FALSE;10621063DEBUGASSERT(data);1064DEBUGASSERT(MAX_SET_COOKIE_AMOUNT <= 255); /* counter is an unsigned char */1065if(data->req.setcookies >= MAX_SET_COOKIE_AMOUNT)1066return NULL;10671068/* First, alloc and init a new struct for it */1069co = calloc(1, sizeof(struct Cookie));1070if(!co)1071return NULL; /* bail out if we are this low on memory */10721073if(httpheader)1074rc = parse_cookie_header(data, co, ci, lineptr, domain, path, secure);1075else1076rc = parse_netscape(co, ci, lineptr, secure);10771078if(rc)1079goto fail;10801081if(co->prefix_secure && !co->secure)1082/* The __Secure- prefix only requires that the cookie be set secure */1083goto fail;10841085if(co->prefix_host) {1086/*1087* The __Host- prefix requires the cookie to be secure, have a "/" path1088* and not have a domain set.1089*/1090if(co->secure && co->path && strcmp(co->path, "/") == 0 && !co->tailmatch)1091;1092else1093goto fail;1094}10951096if(!ci->running && /* read from a file */1097ci->newsession && /* clean session cookies */1098!co->expires) /* this is a session cookie */1099goto fail;11001101co->livecookie = ci->running;1102co->creationtime = ++ci->lastct;11031104/*1105* Now we have parsed the incoming line, we must now check if this supersedes1106* an already existing cookie, which it may if the previous have the same1107* domain and path as this.1108*/11091110/* remove expired cookies */1111if(!noexpire)1112remove_expired(ci);11131114if(is_public_suffix(data, co, domain))1115goto fail;11161117if(replace_existing(data, co, ci, secure, &replaces))1118goto fail;11191120/* add this cookie to the list */1121myhash = cookiehash(co->domain);1122Curl_llist_append(&ci->cookielist[myhash], co, &co->node);11231124if(ci->running)1125/* Only show this when NOT reading the cookies from a file */1126infof(data, "%s cookie %s=\"%s\" for domain %s, path %s, "1127"expire %" FMT_OFF_T,1128replaces ? "Replaced":"Added", co->name, co->value,1129co->domain, co->path, co->expires);11301131if(!replaces)1132ci->numcookies++; /* one more cookie in the jar */11331134/*1135* Now that we have added a new cookie to the jar, update the expiration1136* tracker in case it is the next one to expire.1137*/1138if(co->expires && (co->expires < ci->next_expiration))1139ci->next_expiration = co->expires;11401141if(httpheader)1142data->req.setcookies++;11431144return co;1145fail:1146freecookie(co);1147return NULL;1148}114911501151/*1152* Curl_cookie_init()1153*1154* Inits a cookie struct to read data from a local file. This is always1155* called before any cookies are set. File may be NULL in which case only the1156* struct is initialized. Is file is "-" then STDIN is read.1157*1158* If 'newsession' is TRUE, discard all "session cookies" on read from file.1159*1160* Note that 'data' might be called as NULL pointer. If data is NULL, 'file'1161* will be ignored.1162*1163* Returns NULL on out of memory. Invalid cookies are ignored.1164*/1165struct CookieInfo *Curl_cookie_init(struct Curl_easy *data,1166const char *file,1167struct CookieInfo *ci,1168bool newsession)1169{1170FILE *handle = NULL;11711172if(!ci) {1173int i;11741175/* we did not get a struct, create one */1176ci = calloc(1, sizeof(struct CookieInfo));1177if(!ci)1178return NULL; /* failed to get memory */11791180/* This does not use the destructor callback since we want to add1181and remove to lists while keeping the cookie struct intact */1182for(i = 0; i < COOKIE_HASH_SIZE; i++)1183Curl_llist_init(&ci->cookielist[i], NULL);1184/*1185* Initialize the next_expiration time to signal that we do not have enough1186* information yet.1187*/1188ci->next_expiration = CURL_OFF_T_MAX;1189}1190ci->newsession = newsession; /* new session? */11911192if(data) {1193FILE *fp = NULL;1194if(file && *file) {1195if(!strcmp(file, "-"))1196fp = stdin;1197else {1198fp = curlx_fopen(file, "rb");1199if(!fp)1200infof(data, "WARNING: failed to open cookie file \"%s\"", file);1201else1202handle = fp;1203}1204}12051206ci->running = FALSE; /* this is not running, this is init */1207if(fp) {1208struct dynbuf buf;1209bool eof = FALSE;1210CURLcode result;1211curlx_dyn_init(&buf, MAX_COOKIE_LINE);1212do {1213result = Curl_get_line(&buf, fp, &eof);1214if(!result) {1215const char *lineptr = curlx_dyn_ptr(&buf);1216bool headerline = FALSE;1217if(checkprefix("Set-Cookie:", lineptr)) {1218/* This is a cookie line, get it! */1219lineptr += 11;1220headerline = TRUE;1221curlx_str_passblanks(&lineptr);1222}12231224(void)Curl_cookie_add(data, ci, headerline, TRUE, lineptr, NULL,1225NULL, TRUE);1226/* File reading cookie failures are not propagated back to the1227caller because there is no way to do that */1228}1229} while(!result && !eof);1230curlx_dyn_free(&buf); /* free the line buffer */12311232/*1233* Remove expired cookies from the hash. We must make sure to run this1234* after reading the file, and not on every cookie.1235*/1236remove_expired(ci);12371238if(handle)1239curlx_fclose(handle);1240}1241data->state.cookie_engine = TRUE;1242}1243ci->running = TRUE; /* now, we are running */12441245return ci;1246}12471248/*1249* cookie_sort1250*1251* Helper function to sort cookies such that the longest path gets before the1252* shorter path. Path, domain and name lengths are considered in that order,1253* with the creationtime as the tiebreaker. The creationtime is guaranteed to1254* be unique per cookie, so we know we will get an ordering at that point.1255*/1256static int cookie_sort(const void *p1, const void *p2)1257{1258const struct Cookie *c1 = *(const struct Cookie * const *)p1;1259const struct Cookie *c2 = *(const struct Cookie * const *)p2;1260size_t l1, l2;12611262/* 1 - compare cookie path lengths */1263l1 = c1->path ? strlen(c1->path) : 0;1264l2 = c2->path ? strlen(c2->path) : 0;12651266if(l1 != l2)1267return (l2 > l1) ? 1 : -1; /* avoid size_t <=> int conversions */12681269/* 2 - compare cookie domain lengths */1270l1 = c1->domain ? strlen(c1->domain) : 0;1271l2 = c2->domain ? strlen(c2->domain) : 0;12721273if(l1 != l2)1274return (l2 > l1) ? 1 : -1; /* avoid size_t <=> int conversions */12751276/* 3 - compare cookie name lengths */1277l1 = c1->name ? strlen(c1->name) : 0;1278l2 = c2->name ? strlen(c2->name) : 0;12791280if(l1 != l2)1281return (l2 > l1) ? 1 : -1;12821283/* 4 - compare cookie creation time */1284return (c2->creationtime > c1->creationtime) ? 1 : -1;1285}12861287/*1288* cookie_sort_ct1289*1290* Helper function to sort cookies according to creation time.1291*/1292static int cookie_sort_ct(const void *p1, const void *p2)1293{1294const struct Cookie *c1 = *(const struct Cookie * const *)p1;1295const struct Cookie *c2 = *(const struct Cookie * const *)p2;12961297return (c2->creationtime > c1->creationtime) ? 1 : -1;1298}12991300bool Curl_secure_context(struct connectdata *conn, const char *host)1301{1302return conn->handler->protocol&(CURLPROTO_HTTPS|CURLPROTO_WSS) ||1303curl_strequal("localhost", host) ||1304!strcmp(host, "127.0.0.1") ||1305!strcmp(host, "::1");1306}13071308/*1309* Curl_cookie_getlist1310*1311* For a given host and path, return a linked list of cookies that the client1312* should send to the server if used now. The secure boolean informs the cookie1313* if a secure connection is achieved or not.1314*1315* It shall only return cookies that have not expired.1316*1317* Returns 0 when there is a list returned. Otherwise non-zero.1318*/1319int Curl_cookie_getlist(struct Curl_easy *data,1320struct connectdata *conn,1321const char *host,1322struct Curl_llist *list)1323{1324size_t matches = 0;1325const bool is_ip = Curl_host_is_ipnum(host);1326const size_t myhash = cookiehash(host);1327struct Curl_llist_node *n;1328const bool secure = Curl_secure_context(conn, host);1329struct CookieInfo *ci = data->cookies;1330const char *path = data->state.up.path;13311332Curl_llist_init(list, NULL);13331334if(!ci || !Curl_llist_count(&ci->cookielist[myhash]))1335return 1; /* no cookie struct or no cookies in the struct */13361337/* at first, remove expired cookies */1338remove_expired(ci);13391340for(n = Curl_llist_head(&ci->cookielist[myhash]);1341n; n = Curl_node_next(n)) {1342struct Cookie *co = Curl_node_elem(n);13431344/* if the cookie requires we are secure we must only continue if we are! */1345if(co->secure ? secure : TRUE) {13461347/* now check if the domain is correct */1348if(!co->domain ||1349(co->tailmatch && !is_ip &&1350cookie_tailmatch(co->domain, strlen(co->domain), host)) ||1351((!co->tailmatch || is_ip) && curl_strequal(host, co->domain)) ) {1352/*1353* the right part of the host matches the domain stuff in the1354* cookie data1355*/13561357/*1358* now check the left part of the path with the cookies path1359* requirement1360*/1361if(!co->spath || pathmatch(co->spath, path) ) {13621363/*1364* This is a match and we add it to the return-linked-list1365*/1366Curl_llist_append(list, co, &co->getnode);1367matches++;1368if(matches >= MAX_COOKIE_SEND_AMOUNT) {1369infof(data, "Included max number of cookies (%zu) in request!",1370matches);1371break;1372}1373}1374}1375}1376}13771378if(matches) {1379/*1380* Now we need to make sure that if there is a name appearing more than1381* once, the longest specified path version comes first. To make this1382* the swiftest way, we just sort them all based on path length.1383*/1384struct Cookie **array;1385size_t i;13861387/* alloc an array and store all cookie pointers */1388array = malloc(sizeof(struct Cookie *) * matches);1389if(!array)1390goto fail;13911392n = Curl_llist_head(list);13931394for(i = 0; n; n = Curl_node_next(n))1395array[i++] = Curl_node_elem(n);13961397/* now sort the cookie pointers in path length order */1398qsort(array, matches, sizeof(struct Cookie *), cookie_sort);13991400/* remake the linked list order according to the new order */1401Curl_llist_destroy(list, NULL);14021403for(i = 0; i < matches; i++)1404Curl_llist_append(list, array[i], &array[i]->getnode);14051406free(array); /* remove the temporary data again */1407}14081409return 0; /* success */14101411fail:1412/* failure, clear up the allocated chain and return NULL */1413Curl_llist_destroy(list, NULL);1414return 2; /* error */1415}14161417/*1418* Curl_cookie_clearall1419*1420* Clear all existing cookies and reset the counter.1421*/1422void Curl_cookie_clearall(struct CookieInfo *ci)1423{1424if(ci) {1425unsigned int i;1426for(i = 0; i < COOKIE_HASH_SIZE; i++) {1427struct Curl_llist_node *n;1428for(n = Curl_llist_head(&ci->cookielist[i]); n;) {1429struct Cookie *c = Curl_node_elem(n);1430struct Curl_llist_node *e = Curl_node_next(n);1431Curl_node_remove(n);1432freecookie(c);1433n = e;1434}1435}1436ci->numcookies = 0;1437}1438}14391440/*1441* Curl_cookie_clearsess1442*1443* Free all session cookies in the cookies list.1444*/1445void Curl_cookie_clearsess(struct CookieInfo *ci)1446{1447unsigned int i;14481449if(!ci)1450return;14511452for(i = 0; i < COOKIE_HASH_SIZE; i++) {1453struct Curl_llist_node *n = Curl_llist_head(&ci->cookielist[i]);1454struct Curl_llist_node *e = NULL;14551456for(; n; n = e) {1457struct Cookie *curr = Curl_node_elem(n);1458e = Curl_node_next(n); /* in case the node is removed, get it early */1459if(!curr->expires) {1460Curl_node_remove(n);1461freecookie(curr);1462ci->numcookies--;1463}1464}1465}1466}14671468/*1469* Curl_cookie_cleanup()1470*1471* Free a "cookie object" previous created with Curl_cookie_init().1472*/1473void Curl_cookie_cleanup(struct CookieInfo *ci)1474{1475if(ci) {1476Curl_cookie_clearall(ci);1477free(ci); /* free the base struct as well */1478}1479}14801481/*1482* get_netscape_format()1483*1484* Formats a string for Netscape output file, w/o a newline at the end.1485* Function returns a char * to a formatted line. The caller is responsible1486* for freeing the returned pointer.1487*/1488static char *get_netscape_format(const struct Cookie *co)1489{1490return curl_maprintf(1491"%s" /* httponly preamble */1492"%s%s\t" /* domain */1493"%s\t" /* tailmatch */1494"%s\t" /* path */1495"%s\t" /* secure */1496"%" FMT_OFF_T "\t" /* expires */1497"%s\t" /* name */1498"%s", /* value */1499co->httponly ? "#HttpOnly_" : "",1500/*1501* Make sure all domains are prefixed with a dot if they allow1502* tailmatching. This is Mozilla-style.1503*/1504(co->tailmatch && co->domain && co->domain[0] != '.') ? "." : "",1505co->domain ? co->domain : "unknown",1506co->tailmatch ? "TRUE" : "FALSE",1507co->path ? co->path : "/",1508co->secure ? "TRUE" : "FALSE",1509co->expires,1510co->name,1511co->value ? co->value : "");1512}15131514/*1515* cookie_output()1516*1517* Writes all internally known cookies to the specified file. Specify1518* "-" as filename to write to stdout.1519*1520* The function returns non-zero on write failure.1521*/1522static CURLcode cookie_output(struct Curl_easy *data,1523struct CookieInfo *ci,1524const char *filename)1525{1526FILE *out = NULL;1527bool use_stdout = FALSE;1528char *tempstore = NULL;1529CURLcode error = CURLE_OK;15301531if(!ci)1532/* no cookie engine alive */1533return CURLE_OK;15341535/* at first, remove expired cookies */1536remove_expired(ci);15371538if(!strcmp("-", filename)) {1539/* use stdout */1540out = stdout;1541use_stdout = TRUE;1542}1543else {1544error = Curl_fopen(data, filename, &out, &tempstore);1545if(error)1546goto error;1547}15481549fputs("# Netscape HTTP Cookie File\n"1550"# https://curl.se/docs/http-cookies.html\n"1551"# This file was generated by libcurl! Edit at your own risk.\n\n",1552out);15531554if(ci->numcookies) {1555unsigned int i;1556size_t nvalid = 0;1557struct Cookie **array;1558struct Curl_llist_node *n;15591560array = calloc(1, sizeof(struct Cookie *) * ci->numcookies);1561if(!array) {1562error = CURLE_OUT_OF_MEMORY;1563goto error;1564}15651566/* only sort the cookies with a domain property */1567for(i = 0; i < COOKIE_HASH_SIZE; i++) {1568for(n = Curl_llist_head(&ci->cookielist[i]); n;1569n = Curl_node_next(n)) {1570struct Cookie *co = Curl_node_elem(n);1571if(!co->domain)1572continue;1573array[nvalid++] = co;1574}1575}15761577qsort(array, nvalid, sizeof(struct Cookie *), cookie_sort_ct);15781579for(i = 0; i < nvalid; i++) {1580char *format_ptr = get_netscape_format(array[i]);1581if(!format_ptr) {1582free(array);1583error = CURLE_OUT_OF_MEMORY;1584goto error;1585}1586curl_mfprintf(out, "%s\n", format_ptr);1587free(format_ptr);1588}15891590free(array);1591}15921593if(!use_stdout) {1594curlx_fclose(out);1595out = NULL;1596if(tempstore && Curl_rename(tempstore, filename)) {1597error = CURLE_WRITE_ERROR;1598goto error;1599}1600}16011602/*1603* If we reach here we have successfully written a cookie file so there is1604* no need to inspect the error, any error case should have jumped into the1605* error block below.1606*/1607free(tempstore);1608return CURLE_OK;16091610error:1611if(out && !use_stdout)1612curlx_fclose(out);1613if(tempstore) {1614unlink(tempstore);1615free(tempstore);1616}1617return error;1618}16191620static struct curl_slist *cookie_list(struct Curl_easy *data)1621{1622struct curl_slist *list = NULL;1623struct curl_slist *beg;1624unsigned int i;1625struct Curl_llist_node *n;16261627if(!data->cookies || (data->cookies->numcookies == 0))1628return NULL;16291630/* at first, remove expired cookies */1631remove_expired(data->cookies);16321633for(i = 0; i < COOKIE_HASH_SIZE; i++) {1634for(n = Curl_llist_head(&data->cookies->cookielist[i]); n;1635n = Curl_node_next(n)) {1636struct Cookie *c = Curl_node_elem(n);1637char *line;1638if(!c->domain)1639continue;1640line = get_netscape_format(c);1641if(!line) {1642curl_slist_free_all(list);1643return NULL;1644}1645beg = Curl_slist_append_nodup(list, line);1646if(!beg) {1647free(line);1648curl_slist_free_all(list);1649return NULL;1650}1651list = beg;1652}1653}16541655return list;1656}16571658struct curl_slist *Curl_cookie_list(struct Curl_easy *data)1659{1660struct curl_slist *list;1661Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);1662list = cookie_list(data);1663Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);1664return list;1665}16661667void Curl_flush_cookies(struct Curl_easy *data, bool cleanup)1668{1669CURLcode res;16701671Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);1672/* only save the cookie file if a transfer was started (data->state.url is1673set), as otherwise the cookies were not completely initialized and there1674might be cookie files that weren't loaded so saving the file is the wrong1675thing. */1676if(data->set.str[STRING_COOKIEJAR] && data->state.url) {1677/* if we have a destination file for all the cookies to get dumped to */1678res = cookie_output(data, data->cookies, data->set.str[STRING_COOKIEJAR]);1679if(res)1680infof(data, "WARNING: failed to save cookies in %s: %s",1681data->set.str[STRING_COOKIEJAR], curl_easy_strerror(res));1682}16831684if(cleanup && (!data->share || (data->cookies != data->share->cookies))) {1685Curl_cookie_cleanup(data->cookies);1686data->cookies = NULL;1687}1688Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);1689}16901691#endif /* CURL_DISABLE_HTTP || CURL_DISABLE_COOKIES */169216931694