/***************************************************************************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_get_line.h"83#include "curl_memrchr.h"84#include "parsedate.h"85#include "rename.h"86#include "fopen.h"87#include "strdup.h"88#include "llist.h"89#include "curlx/strparse.h"9091/* The last 3 #include files should be in this order */92#include "curl_printf.h"93#include "curl_memory.h"94#include "memdebug.h"9596static void strstore(char **str, const char *newstr, size_t len);9798/* number of seconds in 400 days */99#define COOKIES_MAXAGE (400*24*3600)100101/* Make sure cookies never expire further away in time than 400 days into the102future. (from RFC6265bis draft-19)103104For the sake of easier testing, align the capped time to an even 60 second105boundary.106*/107static void cap_expires(time_t now, struct Cookie *co)108{109if((TIME_T_MAX - COOKIES_MAXAGE - 30) > now) {110timediff_t cap = now + COOKIES_MAXAGE;111if(co->expires > cap) {112cap += 30;113co->expires = (cap/60)*60;114}115}116}117118static void freecookie(struct Cookie *co)119{120free(co->domain);121free(co->path);122free(co->spath);123free(co->name);124free(co->value);125free(co);126}127128static bool cookie_tailmatch(const char *cookie_domain,129size_t cookie_domain_len,130const char *hostname)131{132size_t hostname_len = strlen(hostname);133134if(hostname_len < cookie_domain_len)135return FALSE;136137if(!strncasecompare(cookie_domain,138hostname + hostname_len-cookie_domain_len,139cookie_domain_len))140return FALSE;141142/*143* A lead char of cookie_domain is not '.'.144* RFC6265 4.1.2.3. The Domain Attribute says:145* For example, if the value of the Domain attribute is146* "example.com", the user agent will include the cookie in the Cookie147* header when making HTTP requests to example.com, www.example.com, and148* www.corp.example.com.149*/150if(hostname_len == cookie_domain_len)151return TRUE;152if('.' == *(hostname + hostname_len - cookie_domain_len - 1))153return TRUE;154return FALSE;155}156157/*158* matching cookie path and URL path159* RFC6265 5.1.4 Paths and Path-Match160*/161static bool pathmatch(const char *cookie_path, const char *uri_path)162{163size_t cookie_path_len;164size_t uri_path_len;165bool ret = FALSE;166167/* cookie_path must not have last '/' separator. ex: /sample */168cookie_path_len = strlen(cookie_path);169if(1 == cookie_path_len) {170/* cookie_path must be '/' */171return TRUE;172}173174/* #-fragments are already cut off! */175if(0 == strlen(uri_path) || uri_path[0] != '/')176uri_path = "/";177178/*179* here, RFC6265 5.1.4 says180* 4. Output the characters of the uri-path from the first character up181* to, but not including, the right-most %x2F ("/").182* but URL path /hoge?fuga=xxx means /hoge/index.cgi?fuga=xxx in some site183* without redirect.184* Ignore this algorithm because /hoge is uri path for this case185* (uri path is not /).186*/187188uri_path_len = strlen(uri_path);189190if(uri_path_len < cookie_path_len)191goto pathmatched;192193/* not using checkprefix() because matching should be case-sensitive */194if(strncmp(cookie_path, uri_path, cookie_path_len))195goto pathmatched;196197/* The cookie-path and the uri-path are identical. */198if(cookie_path_len == uri_path_len) {199ret = TRUE;200goto pathmatched;201}202203/* here, cookie_path_len < uri_path_len */204if(uri_path[cookie_path_len] == '/') {205ret = TRUE;206goto pathmatched;207}208209pathmatched:210return ret;211}212213/*214* Return the top-level domain, for optimal hashing.215*/216static const char *get_top_domain(const char * const domain, size_t *outlen)217{218size_t len = 0;219const char *first = NULL, *last;220221if(domain) {222len = strlen(domain);223last = memrchr(domain, '.', len);224if(last) {225first = memrchr(domain, '.', (last - domain));226if(first)227len -= (++first - domain);228}229}230231if(outlen)232*outlen = len;233234return first ? first : domain;235}236237/* Avoid C1001, an "internal error" with MSVC14 */238#if defined(_MSC_VER) && (_MSC_VER == 1900)239#pragma optimize("", off)240#endif241242/*243* A case-insensitive hash for the cookie domains.244*/245static size_t cookie_hash_domain(const char *domain, const size_t len)246{247const char *end = domain + len;248size_t h = 5381;249250while(domain < end) {251size_t j = (size_t)Curl_raw_toupper(*domain++);252h += h << 5;253h ^= j;254}255256return (h % COOKIE_HASH_SIZE);257}258259#if defined(_MSC_VER) && (_MSC_VER == 1900)260#pragma optimize("", on)261#endif262263/*264* Hash this domain.265*/266static size_t cookiehash(const char * const domain)267{268const char *top;269size_t len;270271if(!domain || Curl_host_is_ipnum(domain))272return 0;273274top = get_top_domain(domain, &len);275return cookie_hash_domain(top, len);276}277278/*279* cookie path sanitize280*/281static char *sanitize_cookie_path(const char *cookie_path)282{283size_t len = strlen(cookie_path);284285/* some sites send path attribute within '"'. */286if(cookie_path[0] == '\"') {287cookie_path++;288len--;289}290if(len && (cookie_path[len - 1] == '\"'))291len--;292293/* RFC6265 5.2.4 The Path Attribute */294if(cookie_path[0] != '/')295/* Let cookie-path be the default-path. */296return strdup("/");297298/* remove trailing slash */299/* convert /hoge/ to /hoge */300if(len && cookie_path[len - 1] == '/')301len--;302303return Curl_memdup0(cookie_path, len);304}305306/*307* Load cookies from all given cookie files (CURLOPT_COOKIEFILE).308*309* NOTE: OOM or cookie parsing failures are ignored.310*/311void Curl_cookie_loadfiles(struct Curl_easy *data)312{313struct curl_slist *list = data->state.cookielist;314if(list) {315Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);316while(list) {317struct CookieInfo *ci =318Curl_cookie_init(data, list->data, data->cookies,319data->set.cookiesession);320if(!ci)321/*322* Failure may be due to OOM or a bad cookie; both are ignored323* but only the first should be324*/325infof(data, "ignoring failed cookie_init for %s", list->data);326else327data->cookies = ci;328list = list->next;329}330Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);331}332}333334/*335* strstore336*337* A thin wrapper around strdup which ensures that any memory allocated at338* *str will be freed before the string allocated by strdup is stored there.339* The intended usecase is repeated assignments to the same variable during340* parsing in a last-wins scenario. The caller is responsible for checking341* for OOM errors.342*/343static void strstore(char **str, const char *newstr, size_t len)344{345DEBUGASSERT(str);346free(*str);347if(!len) {348len++;349newstr = "";350}351*str = Curl_memdup0(newstr, len);352}353354/*355* remove_expired356*357* Remove expired cookies from the hash by inspecting the expires timestamp on358* each cookie in the hash, freeing and deleting any where the timestamp is in359* the past. If the cookiejar has recorded the next timestamp at which one or360* more cookies expire, then processing will exit early in case this timestamp361* is in the future.362*/363static void remove_expired(struct CookieInfo *ci)364{365struct Cookie *co;366curl_off_t now = (curl_off_t)time(NULL);367unsigned int i;368369/*370* If the earliest expiration timestamp in the jar is in the future we can371* skip scanning the whole jar and instead exit early as there will not be372* any cookies to evict. If we need to evict however, reset the373* next_expiration counter in order to track the next one. In case the374* recorded first expiration is the max offset, then perform the safe375* fallback of checking all cookies.376*/377if(now < ci->next_expiration &&378ci->next_expiration != CURL_OFF_T_MAX)379return;380else381ci->next_expiration = CURL_OFF_T_MAX;382383for(i = 0; i < COOKIE_HASH_SIZE; i++) {384struct Curl_llist_node *n;385struct Curl_llist_node *e = NULL;386387for(n = Curl_llist_head(&ci->cookielist[i]); n; n = e) {388co = Curl_node_elem(n);389e = Curl_node_next(n);390if(co->expires && co->expires < now) {391Curl_node_remove(n);392freecookie(co);393ci->numcookies--;394}395else {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*/400if(co->expires && co->expires < ci->next_expiration)401ci->next_expiration = co->expires;402}403}404}405}406407#ifndef USE_LIBPSL408/* Make sure domain contains a dot or is localhost. */409static bool bad_domain(const char *domain, size_t len)410{411if((len == 9) && strncasecompare(domain, "localhost", 9))412return FALSE;413else {414/* there must be a dot present, but that dot must not be a trailing dot */415char *dot = memchr(domain, '.', len);416if(dot) {417size_t i = dot - domain;418if((len - i) > 1)419/* the dot is not the last byte */420return FALSE;421}422}423return TRUE;424}425#endif426427/*428RFC 6265 section 4.1.1 says a server should accept this range:429430cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E431432But Firefox and Chrome as of June 2022 accept space, comma and double-quotes433fine. The prime reason for filtering out control bytes is that some HTTP434servers return 400 for requests that contain such.435*/436static bool invalid_octets(const char *ptr)437{438const unsigned char *p = (const unsigned char *)ptr;439/* Reject all bytes \x01 - \x1f (*except* \x09, TAB) + \x7f */440while(*p) {441if(((*p != 9) && (*p < 0x20)) || (*p == 0x7f))442return TRUE;443p++;444}445return FALSE;446}447448#define CERR_OK 0449#define CERR_TOO_LONG 1 /* input line too long */450#define CERR_TAB 2 /* in a wrong place */451#define CERR_TOO_BIG 3 /* name/value too large */452#define CERR_BAD 4 /* deemed incorrect */453#define CERR_NO_SEP 5 /* semicolon problem */454#define CERR_NO_NAME_VALUE 6 /* name or value problem */455#define CERR_INVALID_OCTET 7 /* bad content */456#define CERR_BAD_SECURE 8 /* secure in a bad place */457#define CERR_OUT_OF_MEMORY 9458#define CERR_NO_TAILMATCH 10459#define CERR_COMMENT 11 /* a commented line */460#define CERR_RANGE 12 /* expire range problem */461#define CERR_FIELDS 13 /* incomplete netscape line */462#ifdef USE_LIBPSL463#define CERR_PSL 14 /* a public suffix */464#endif465#define CERR_LIVE_WINS 15466467/* The maximum length we accept a date string for the 'expire' keyword. The468standard date formats are within the 30 bytes range. This adds an extra469margin just to make sure it realistically works with what is used out470there.471*/472#define MAX_DATE_LENGTH 80473474static int475parse_cookie_header(struct Curl_easy *data,476struct Cookie *co,477struct CookieInfo *ci,478const char *ptr,479const char *domain, /* default domain */480const char *path, /* full path used when this cookie is481set, used to get default path for482the cookie unless set */483bool secure) /* TRUE if connection is over secure484origin */485{486/* This line was read off an HTTP-header */487time_t now;488size_t linelength = strlen(ptr);489if(linelength > MAX_COOKIE_LINE)490/* discard overly long lines at once */491return CERR_TOO_LONG;492493now = time(NULL);494do {495struct Curl_str name;496struct Curl_str val;497498/* we have a <name>=<value> pair or a stand-alone word here */499if(!curlx_str_cspn(&ptr, &name, ";\t\r\n=")) {500bool done = FALSE;501bool sep = FALSE;502curlx_str_trimblanks(&name);503504if(!curlx_str_single(&ptr, '=')) {505sep = TRUE; /* a '=' was used */506if(!curlx_str_cspn(&ptr, &val, ";\r\n")) {507curlx_str_trimblanks(&val);508509/* Reject cookies with a TAB inside the value */510if(memchr(curlx_str(&val), '\t', curlx_strlen(&val))) {511infof(data, "cookie contains TAB, dropping");512return CERR_TAB;513}514}515}516else {517curlx_str_init(&val);518}519520/*521* Check for too long individual name or contents, or too long522* combination of name + contents. Chrome and Firefox support 4095 or523* 4096 bytes combo524*/525if(curlx_strlen(&name) >= (MAX_NAME-1) ||526curlx_strlen(&val) >= (MAX_NAME-1) ||527((curlx_strlen(&name) + curlx_strlen(&val)) > MAX_NAME)) {528infof(data, "oversized cookie dropped, name/val %zu + %zu bytes",529curlx_strlen(&name), curlx_strlen(&val));530return CERR_TOO_BIG;531}532533/*534* Check if we have a reserved prefix set before anything else, as we535* otherwise have to test for the prefix in both the cookie name and536* "the rest". Prefixes must start with '__' and end with a '-', so537* only test for names where that can possibly be true.538*/539if(!strncmp("__Secure-", curlx_str(&name), 9))540co->prefix_secure = TRUE;541else if(!strncmp("__Host-", curlx_str(&name), 7))542co->prefix_host = TRUE;543544/*545* Use strstore() below to properly deal with received cookie546* headers that have the same string property set more than once,547* and then we use the last one.548*/549550if(!co->name) {551/* The very first name/value pair is the actual cookie name */552if(!sep)553/* Bad name/value pair. */554return CERR_NO_SEP;555556strstore(&co->name, curlx_str(&name), curlx_strlen(&name));557strstore(&co->value, curlx_str(&val), curlx_strlen(&val));558done = TRUE;559if(!co->name || !co->value)560return CERR_NO_NAME_VALUE;561562if(invalid_octets(co->value) || invalid_octets(co->name)) {563infof(data, "invalid octets in name/value, cookie dropped");564return CERR_INVALID_OCTET;565}566}567else if(!curlx_strlen(&val)) {568/*569* this was a "<name>=" with no content, and we must allow570* 'secure' and 'httponly' specified this weirdly571*/572done = TRUE;573/*574* secure cookies are only allowed to be set when the connection is575* using a secure protocol, or when the cookie is being set by576* reading from file577*/578if(curlx_str_casecompare(&name, "secure")) {579if(secure || !ci->running) {580co->secure = TRUE;581}582else {583return CERR_BAD_SECURE;584}585}586else if(curlx_str_casecompare(&name, "httponly"))587co->httponly = TRUE;588else if(sep)589/* there was a '=' so we are not done parsing this field */590done = FALSE;591}592if(done)593;594else if(curlx_str_casecompare(&name, "path")) {595strstore(&co->path, curlx_str(&val), curlx_strlen(&val));596if(!co->path)597return CERR_OUT_OF_MEMORY;598free(co->spath); /* if this is set again */599co->spath = sanitize_cookie_path(co->path);600if(!co->spath)601return CERR_OUT_OF_MEMORY;602}603else if(curlx_str_casecompare(&name, "domain") && curlx_strlen(&val)) {604bool is_ip;605const char *v = curlx_str(&val);606/*607* Now, we make sure that our host is within the given domain, or608* the given domain is not valid and thus cannot be set.609*/610611if('.' == *v)612curlx_str_nudge(&val, 1);613614#ifndef USE_LIBPSL615/*616* Without PSL we do not know when the incoming cookie is set on a617* TLD or otherwise "protected" suffix. To reduce risk, we require a618* dot OR the exact hostname being "localhost".619*/620if(bad_domain(curlx_str(&val), curlx_strlen(&val)))621domain = ":";622#endif623624is_ip = Curl_host_is_ipnum(domain ? domain : curlx_str(&val));625626if(!domain627|| (is_ip && !strncmp(curlx_str(&val), domain,628curlx_strlen(&val)) &&629(curlx_strlen(&val) == strlen(domain)))630|| (!is_ip && cookie_tailmatch(curlx_str(&val),631curlx_strlen(&val), domain))) {632strstore(&co->domain, curlx_str(&val), curlx_strlen(&val));633if(!co->domain)634return CERR_OUT_OF_MEMORY;635636if(!is_ip)637co->tailmatch = TRUE; /* we always do that if the domain name was638given */639}640else {641/*642* We did not get a tailmatch and then the attempted set domain is643* not a domain to which the current host belongs. Mark as bad.644*/645infof(data, "skipped cookie with bad tailmatch domain: %s",646curlx_str(&val));647return CERR_NO_TAILMATCH;648}649}650else if(curlx_str_casecompare(&name, "version")) {651/* just ignore */652}653else if(curlx_str_casecompare(&name, "max-age") && curlx_strlen(&val)) {654/*655* Defined in RFC2109:656*657* Optional. The Max-Age attribute defines the lifetime of the658* cookie, in seconds. The delta-seconds value is a decimal non-659* negative integer. After delta-seconds seconds elapse, the660* client should discard the cookie. A value of zero means the661* cookie should be discarded immediately.662*/663int rc;664const char *maxage = curlx_str(&val);665if(*maxage == '\"')666maxage++;667rc = curlx_str_number(&maxage, &co->expires, CURL_OFF_T_MAX);668669switch(rc) {670case STRE_OVERFLOW:671/* overflow, used max value */672co->expires = CURL_OFF_T_MAX;673break;674default:675/* negative or otherwise bad, expire */676co->expires = 1;677break;678case STRE_OK:679if(!co->expires)680/* already expired */681co->expires = 1;682else if(CURL_OFF_T_MAX - now < co->expires)683/* would overflow */684co->expires = CURL_OFF_T_MAX;685else686co->expires += now;687break;688}689cap_expires(now, co);690}691else if(curlx_str_casecompare(&name, "expires") && curlx_strlen(&val)) {692if(!co->expires && (curlx_strlen(&val) < MAX_DATE_LENGTH)) {693/*694* Let max-age have priority.695*696* If the date cannot get parsed for whatever reason, the cookie697* will be treated as a session cookie698*/699char dbuf[MAX_DATE_LENGTH + 1];700memcpy(dbuf, curlx_str(&val), curlx_strlen(&val));701dbuf[curlx_strlen(&val)] = 0;702co->expires = Curl_getdate_capped(dbuf);703704/*705* Session cookies have expires set to 0 so if we get that back706* from the date parser let's add a second to make it a707* non-session cookie708*/709if(co->expires == 0)710co->expires = 1;711else if(co->expires < 0)712co->expires = 0;713cap_expires(now, co);714}715}716717/*718* Else, this is the second (or more) name we do not know about!719*/720}721722if(curlx_str_single(&ptr, ';'))723break;724} while(1);725726if(!co->domain && domain) {727/* no domain was given in the header line, set the default */728co->domain = strdup(domain);729if(!co->domain)730return CERR_OUT_OF_MEMORY;731}732733if(!co->path && path) {734/*735* No path was given in the header line, set the default.736*/737const char *endslash = strrchr(path, '/');738if(endslash) {739size_t pathlen = (endslash - path + 1); /* include end slash */740co->path = Curl_memdup0(path, pathlen);741if(co->path) {742co->spath = sanitize_cookie_path(co->path);743if(!co->spath)744return CERR_OUT_OF_MEMORY;745}746else747return CERR_OUT_OF_MEMORY;748}749}750751/*752* If we did not get a cookie name, or a bad one, the this is an illegal753* line so bail out.754*/755if(!co->name)756return CERR_BAD;757758data->req.setcookies++;759return CERR_OK;760}761762static int763parse_netscape(struct Cookie *co,764struct CookieInfo *ci,765const char *lineptr,766bool secure) /* TRUE if connection is over secure767origin */768{769/*770* This line is NOT an HTTP header style line, we do offer support for771* reading the odd netscape cookies-file format here772*/773const char *ptr, *next;774int fields;775size_t len;776777/*778* In 2008, Internet Explorer introduced HTTP-only cookies to prevent XSS779* attacks. Cookies marked httpOnly are not accessible to JavaScript. In780* Firefox's cookie files, they are prefixed #HttpOnly_ and the rest781* remains as usual, so we skip 10 characters of the line.782*/783if(strncmp(lineptr, "#HttpOnly_", 10) == 0) {784lineptr += 10;785co->httponly = TRUE;786}787788if(lineptr[0]=='#')789/* do not even try the comments */790return CERR_COMMENT;791792/*793* Now loop through the fields and init the struct we already have794* allocated795*/796fields = 0;797for(next = lineptr; next; fields++) {798ptr = next;799len = strcspn(ptr, "\t\r\n");800next = (ptr[len] == '\t' ? &ptr[len + 1] : NULL);801switch(fields) {802case 0:803if(ptr[0]=='.') { /* skip preceding dots */804ptr++;805len--;806}807co->domain = Curl_memdup0(ptr, len);808if(!co->domain)809return CERR_OUT_OF_MEMORY;810break;811case 1:812/*813* flag: A TRUE/FALSE value indicating if all machines within a given814* domain can access the variable. Set TRUE when the cookie says815* .example.com and to false when the domain is complete www.example.com816*/817co->tailmatch = !!strncasecompare(ptr, "TRUE", len);818break;819case 2:820/* The file format allows the path field to remain not filled in */821if(strncmp("TRUE", ptr, len) && strncmp("FALSE", ptr, len)) {822/* only if the path does not look like a boolean option! */823co->path = Curl_memdup0(ptr, len);824if(!co->path)825return CERR_OUT_OF_MEMORY;826else {827co->spath = sanitize_cookie_path(co->path);828if(!co->spath)829return CERR_OUT_OF_MEMORY;830}831break;832}833/* this does not look like a path, make one up! */834co->path = strdup("/");835if(!co->path)836return CERR_OUT_OF_MEMORY;837co->spath = strdup("/");838if(!co->spath)839return CERR_OUT_OF_MEMORY;840fields++; /* add a field and fall down to secure */841FALLTHROUGH();842case 3:843co->secure = FALSE;844if(strncasecompare(ptr, "TRUE", len)) {845if(secure || ci->running)846co->secure = TRUE;847else848return CERR_BAD_SECURE;849}850break;851case 4:852if(curlx_str_number(&ptr, &co->expires, CURL_OFF_T_MAX))853return CERR_RANGE;854break;855case 5:856co->name = Curl_memdup0(ptr, len);857if(!co->name)858return CERR_OUT_OF_MEMORY;859else {860/* For Netscape file format cookies we check prefix on the name */861if(strncasecompare("__Secure-", co->name, 9))862co->prefix_secure = TRUE;863else if(strncasecompare("__Host-", co->name, 7))864co->prefix_host = TRUE;865}866break;867case 6:868co->value = Curl_memdup0(ptr, len);869if(!co->value)870return CERR_OUT_OF_MEMORY;871break;872}873}874if(6 == fields) {875/* we got a cookie with blank contents, fix it */876co->value = strdup("");877if(!co->value)878return CERR_OUT_OF_MEMORY;879else880fields++;881}882883if(7 != fields)884/* we did not find the sufficient number of fields */885return CERR_FIELDS;886887return CERR_OK;888}889890static int891is_public_suffix(struct Curl_easy *data,892struct Cookie *co,893const char *domain)894{895#ifdef USE_LIBPSL896/*897* Check if the domain is a Public Suffix and if yes, ignore the cookie. We898* must also check that the data handle is not NULL since the psl code will899* dereference it.900*/901DEBUGF(infof(data, "PSL check set-cookie '%s' for domain=%s in %s",902co->name, co->domain, domain));903if(data && (domain && co->domain && !Curl_host_is_ipnum(co->domain))) {904bool acceptable = FALSE;905char lcase[256];906char lcookie[256];907size_t dlen = strlen(domain);908size_t clen = strlen(co->domain);909if((dlen < sizeof(lcase)) && (clen < sizeof(lcookie))) {910const psl_ctx_t *psl = Curl_psl_use(data);911if(psl) {912/* the PSL check requires lowercase domain name and pattern */913Curl_strntolower(lcase, domain, dlen + 1);914Curl_strntolower(lcookie, co->domain, clen + 1);915acceptable = psl_is_cookie_domain_acceptable(psl, lcase, lcookie);916Curl_psl_release(data);917}918else919infof(data, "libpsl problem, rejecting cookie for satety");920}921922if(!acceptable) {923infof(data, "cookie '%s' dropped, domain '%s' must not "924"set cookies for '%s'", co->name, domain, co->domain);925return CERR_PSL;926}927}928#else929(void)data;930(void)co;931(void)domain;932DEBUGF(infof(data, "NO PSL to check set-cookie '%s' for domain=%s in %s",933co->name, co->domain, domain));934#endif935return CERR_OK;936}937938static int939replace_existing(struct Curl_easy *data,940struct Cookie *co,941struct CookieInfo *ci,942bool secure,943bool *replacep)944{945bool replace_old = FALSE;946struct Curl_llist_node *replace_n = NULL;947struct Curl_llist_node *n;948size_t myhash = cookiehash(co->domain);949for(n = Curl_llist_head(&ci->cookielist[myhash]); n; n = Curl_node_next(n)) {950struct Cookie *clist = Curl_node_elem(n);951if(!strcmp(clist->name, co->name)) {952/* the names are identical */953bool matching_domains = FALSE;954955if(clist->domain && co->domain) {956if(strcasecompare(clist->domain, co->domain))957/* The domains are identical */958matching_domains = TRUE;959}960else if(!clist->domain && !co->domain)961matching_domains = TRUE;962963if(matching_domains && /* the domains were identical */964clist->spath && co->spath && /* both have paths */965clist->secure && !co->secure && !secure) {966size_t cllen;967const char *sep;968969/*970* A non-secure cookie may not overlay an existing secure cookie.971* For an existing cookie "a" with path "/login", refuse a new972* cookie "a" with for example path "/login/en", while the path973* "/loginhelper" is ok.974*/975976sep = strchr(clist->spath + 1, '/');977978if(sep)979cllen = sep - clist->spath;980else981cllen = strlen(clist->spath);982983if(strncasecompare(clist->spath, co->spath, cllen)) {984infof(data, "cookie '%s' for domain '%s' dropped, would "985"overlay an existing cookie", co->name, co->domain);986return CERR_BAD_SECURE;987}988}989}990991if(!replace_n && !strcmp(clist->name, co->name)) {992/* the names are identical */993994if(clist->domain && co->domain) {995if(strcasecompare(clist->domain, co->domain) &&996(clist->tailmatch == co->tailmatch))997/* The domains are identical */998replace_old = TRUE;999}1000else if(!clist->domain && !co->domain)1001replace_old = TRUE;10021003if(replace_old) {1004/* the domains were identical */10051006if(clist->spath && co->spath &&1007!strcasecompare(clist->spath, co->spath))1008replace_old = FALSE;1009else if(!clist->spath != !co->spath)1010replace_old = FALSE;1011}10121013if(replace_old && !co->livecookie && clist->livecookie) {1014/*1015* Both cookies matched fine, except that the already present cookie1016* is "live", which means it was set from a header, while the new one1017* was read from a file and thus is not "live". "live" cookies are1018* preferred so the new cookie is freed.1019*/1020return CERR_LIVE_WINS;1021}1022if(replace_old)1023replace_n = n;1024}1025}1026if(replace_n) {1027struct Cookie *repl = Curl_node_elem(replace_n);10281029/* when replacing, creationtime is kept from old */1030co->creationtime = repl->creationtime;10311032/* unlink the old */1033Curl_node_remove(replace_n);10341035/* free the old cookie */1036freecookie(repl);1037}1038*replacep = replace_old;1039return CERR_OK;1040}10411042/*1043* Curl_cookie_add1044*1045* Add a single cookie line to the cookie keeping object. Be aware that1046* sometimes we get an IP-only hostname, and that might also be a numerical1047* IPv6 address.1048*1049* Returns NULL on out of memory or invalid cookie. This is suboptimal,1050* as they should be treated separately.1051*/1052struct Cookie *1053Curl_cookie_add(struct Curl_easy *data,1054struct CookieInfo *ci,1055bool httpheader, /* TRUE if HTTP header-style line */1056bool noexpire, /* if TRUE, skip remove_expired() */1057const char *lineptr, /* first character of the line */1058const char *domain, /* default domain */1059const char *path, /* full path used when this cookie is set,1060used to get default path for the cookie1061unless set */1062bool secure) /* TRUE if connection is over secure origin */1063{1064struct Cookie *co;1065size_t myhash;1066int rc;1067bool replaces = FALSE;10681069DEBUGASSERT(data);1070DEBUGASSERT(MAX_SET_COOKIE_AMOUNT <= 255); /* counter is an unsigned char */1071if(data->req.setcookies >= MAX_SET_COOKIE_AMOUNT)1072return NULL;10731074/* First, alloc and init a new struct for it */1075co = calloc(1, sizeof(struct Cookie));1076if(!co)1077return NULL; /* bail out if we are this low on memory */10781079if(httpheader)1080rc = parse_cookie_header(data, co, ci, lineptr, domain, path, secure);1081else1082rc = parse_netscape(co, ci, lineptr, secure);10831084if(rc)1085goto fail;10861087if(co->prefix_secure && !co->secure)1088/* The __Secure- prefix only requires that the cookie be set secure */1089goto fail;10901091if(co->prefix_host) {1092/*1093* The __Host- prefix requires the cookie to be secure, have a "/" path1094* and not have a domain set.1095*/1096if(co->secure && co->path && strcmp(co->path, "/") == 0 && !co->tailmatch)1097;1098else1099goto fail;1100}11011102if(!ci->running && /* read from a file */1103ci->newsession && /* clean session cookies */1104!co->expires) /* this is a session cookie since it does not expire */1105goto fail;11061107co->livecookie = ci->running;1108co->creationtime = ++ci->lastct;11091110/*1111* Now we have parsed the incoming line, we must now check if this supersedes1112* an already existing cookie, which it may if the previous have the same1113* domain and path as this.1114*/11151116/* remove expired cookies */1117if(!noexpire)1118remove_expired(ci);11191120if(is_public_suffix(data, co, domain))1121goto fail;11221123if(replace_existing(data, co, ci, secure, &replaces))1124goto fail;11251126/* add this cookie to the list */1127myhash = cookiehash(co->domain);1128Curl_llist_append(&ci->cookielist[myhash], co, &co->node);11291130if(ci->running)1131/* Only show this when NOT reading the cookies from a file */1132infof(data, "%s cookie %s=\"%s\" for domain %s, path %s, "1133"expire %" FMT_OFF_T,1134replaces ? "Replaced":"Added", co->name, co->value,1135co->domain, co->path, co->expires);11361137if(!replaces)1138ci->numcookies++; /* one more cookie in the jar */11391140/*1141* Now that we have added a new cookie to the jar, update the expiration1142* tracker in case it is the next one to expire.1143*/1144if(co->expires && (co->expires < ci->next_expiration))1145ci->next_expiration = co->expires;11461147return co;1148fail:1149freecookie(co);1150return NULL;1151}115211531154/*1155* Curl_cookie_init()1156*1157* Inits a cookie struct to read data from a local file. This is always1158* called before any cookies are set. File may be NULL in which case only the1159* struct is initialized. Is file is "-" then STDIN is read.1160*1161* If 'newsession' is TRUE, discard all "session cookies" on read from file.1162*1163* Note that 'data' might be called as NULL pointer. If data is NULL, 'file'1164* will be ignored.1165*1166* Returns NULL on out of memory. Invalid cookies are ignored.1167*/1168struct CookieInfo *Curl_cookie_init(struct Curl_easy *data,1169const char *file,1170struct CookieInfo *ci,1171bool newsession)1172{1173FILE *handle = NULL;11741175if(!ci) {1176int i;11771178/* we did not get a struct, create one */1179ci = calloc(1, sizeof(struct CookieInfo));1180if(!ci)1181return NULL; /* failed to get memory */11821183/* This does not use the destructor callback since we want to add1184and remove to lists while keeping the cookie struct intact */1185for(i = 0; i < COOKIE_HASH_SIZE; i++)1186Curl_llist_init(&ci->cookielist[i], NULL);1187/*1188* Initialize the next_expiration time to signal that we do not have enough1189* information yet.1190*/1191ci->next_expiration = CURL_OFF_T_MAX;1192}1193ci->newsession = newsession; /* new session? */11941195if(data) {1196FILE *fp = NULL;1197if(file && *file) {1198if(!strcmp(file, "-"))1199fp = stdin;1200else {1201fp = fopen(file, "rb");1202if(!fp)1203infof(data, "WARNING: failed to open cookie file \"%s\"", file);1204else1205handle = fp;1206}1207}12081209ci->running = FALSE; /* this is not running, this is init */1210if(fp) {1211struct dynbuf buf;1212curlx_dyn_init(&buf, MAX_COOKIE_LINE);1213while(Curl_get_line(&buf, fp)) {1214const char *lineptr = curlx_dyn_ptr(&buf);1215bool headerline = FALSE;1216if(checkprefix("Set-Cookie:", lineptr)) {1217/* This is a cookie line, get it! */1218lineptr += 11;1219headerline = TRUE;1220curlx_str_passblanks(&lineptr);1221}12221223Curl_cookie_add(data, ci, headerline, TRUE, lineptr, NULL, NULL, TRUE);1224}1225curlx_dyn_free(&buf); /* free the line buffer */12261227/*1228* Remove expired cookies from the hash. We must make sure to run this1229* after reading the file, and not on every cookie.1230*/1231remove_expired(ci);12321233if(handle)1234fclose(handle);1235}1236data->state.cookie_engine = TRUE;1237}1238ci->running = TRUE; /* now, we are running */12391240return ci;1241}12421243/*1244* cookie_sort1245*1246* Helper function to sort cookies such that the longest path gets before the1247* shorter path. Path, domain and name lengths are considered in that order,1248* with the creationtime as the tiebreaker. The creationtime is guaranteed to1249* be unique per cookie, so we know we will get an ordering at that point.1250*/1251static int cookie_sort(const void *p1, const void *p2)1252{1253const struct Cookie *c1 = *(const struct Cookie * const *)p1;1254const struct Cookie *c2 = *(const struct Cookie * const *)p2;1255size_t l1, l2;12561257/* 1 - compare cookie path lengths */1258l1 = c1->path ? strlen(c1->path) : 0;1259l2 = c2->path ? strlen(c2->path) : 0;12601261if(l1 != l2)1262return (l2 > l1) ? 1 : -1; /* avoid size_t <=> int conversions */12631264/* 2 - compare cookie domain lengths */1265l1 = c1->domain ? strlen(c1->domain) : 0;1266l2 = c2->domain ? strlen(c2->domain) : 0;12671268if(l1 != l2)1269return (l2 > l1) ? 1 : -1; /* avoid size_t <=> int conversions */12701271/* 3 - compare cookie name lengths */1272l1 = c1->name ? strlen(c1->name) : 0;1273l2 = c2->name ? strlen(c2->name) : 0;12741275if(l1 != l2)1276return (l2 > l1) ? 1 : -1;12771278/* 4 - compare cookie creation time */1279return (c2->creationtime > c1->creationtime) ? 1 : -1;1280}12811282/*1283* cookie_sort_ct1284*1285* Helper function to sort cookies according to creation time.1286*/1287static int cookie_sort_ct(const void *p1, const void *p2)1288{1289const struct Cookie *c1 = *(const struct Cookie * const *)p1;1290const struct Cookie *c2 = *(const struct Cookie * const *)p2;12911292return (c2->creationtime > c1->creationtime) ? 1 : -1;1293}12941295/*1296* Curl_cookie_getlist1297*1298* For a given host and path, return a linked list of cookies that the client1299* should send to the server if used now. The secure boolean informs the cookie1300* if a secure connection is achieved or not.1301*1302* It shall only return cookies that have not expired.1303*1304* Returns 0 when there is a list returned. Otherwise non-zero.1305*/1306int Curl_cookie_getlist(struct Curl_easy *data,1307struct CookieInfo *ci,1308const char *host, const char *path,1309bool secure,1310struct Curl_llist *list)1311{1312size_t matches = 0;1313bool is_ip;1314const size_t myhash = cookiehash(host);1315struct Curl_llist_node *n;13161317Curl_llist_init(list, NULL);13181319if(!ci || !Curl_llist_count(&ci->cookielist[myhash]))1320return 1; /* no cookie struct or no cookies in the struct */13211322/* at first, remove expired cookies */1323remove_expired(ci);13241325/* check if host is an IP(v4|v6) address */1326is_ip = Curl_host_is_ipnum(host);13271328for(n = Curl_llist_head(&ci->cookielist[myhash]);1329n; n = Curl_node_next(n)) {1330struct Cookie *co = Curl_node_elem(n);13311332/* if the cookie requires we are secure we must only continue if we are! */1333if(co->secure ? secure : TRUE) {13341335/* now check if the domain is correct */1336if(!co->domain ||1337(co->tailmatch && !is_ip &&1338cookie_tailmatch(co->domain, strlen(co->domain), host)) ||1339((!co->tailmatch || is_ip) && strcasecompare(host, co->domain)) ) {1340/*1341* the right part of the host matches the domain stuff in the1342* cookie data1343*/13441345/*1346* now check the left part of the path with the cookies path1347* requirement1348*/1349if(!co->spath || pathmatch(co->spath, path) ) {13501351/*1352* This is a match and we add it to the return-linked-list1353*/1354Curl_llist_append(list, co, &co->getnode);1355matches++;1356if(matches >= MAX_COOKIE_SEND_AMOUNT) {1357infof(data, "Included max number of cookies (%zu) in request!",1358matches);1359break;1360}1361}1362}1363}1364}13651366if(matches) {1367/*1368* Now we need to make sure that if there is a name appearing more than1369* once, the longest specified path version comes first. To make this1370* the swiftest way, we just sort them all based on path length.1371*/1372struct Cookie **array;1373size_t i;13741375/* alloc an array and store all cookie pointers */1376array = malloc(sizeof(struct Cookie *) * matches);1377if(!array)1378goto fail;13791380n = Curl_llist_head(list);13811382for(i = 0; n; n = Curl_node_next(n))1383array[i++] = Curl_node_elem(n);13841385/* now sort the cookie pointers in path length order */1386qsort(array, matches, sizeof(struct Cookie *), cookie_sort);13871388/* remake the linked list order according to the new order */1389Curl_llist_destroy(list, NULL);13901391for(i = 0; i < matches; i++)1392Curl_llist_append(list, array[i], &array[i]->getnode);13931394free(array); /* remove the temporary data again */1395}13961397return 0; /* success */13981399fail:1400/* failure, clear up the allocated chain and return NULL */1401Curl_llist_destroy(list, NULL);1402return 2; /* error */1403}14041405/*1406* Curl_cookie_clearall1407*1408* Clear all existing cookies and reset the counter.1409*/1410void Curl_cookie_clearall(struct CookieInfo *ci)1411{1412if(ci) {1413unsigned int i;1414for(i = 0; i < COOKIE_HASH_SIZE; i++) {1415struct Curl_llist_node *n;1416for(n = Curl_llist_head(&ci->cookielist[i]); n;) {1417struct Cookie *c = Curl_node_elem(n);1418struct Curl_llist_node *e = Curl_node_next(n);1419Curl_node_remove(n);1420freecookie(c);1421n = e;1422}1423}1424ci->numcookies = 0;1425}1426}14271428/*1429* Curl_cookie_clearsess1430*1431* Free all session cookies in the cookies list.1432*/1433void Curl_cookie_clearsess(struct CookieInfo *ci)1434{1435unsigned int i;14361437if(!ci)1438return;14391440for(i = 0; i < COOKIE_HASH_SIZE; i++) {1441struct Curl_llist_node *n = Curl_llist_head(&ci->cookielist[i]);1442struct Curl_llist_node *e = NULL;14431444for(; n; n = e) {1445struct Cookie *curr = Curl_node_elem(n);1446e = Curl_node_next(n); /* in case the node is removed, get it early */1447if(!curr->expires) {1448Curl_node_remove(n);1449freecookie(curr);1450ci->numcookies--;1451}1452}1453}1454}14551456/*1457* Curl_cookie_cleanup()1458*1459* Free a "cookie object" previous created with Curl_cookie_init().1460*/1461void Curl_cookie_cleanup(struct CookieInfo *ci)1462{1463if(ci) {1464Curl_cookie_clearall(ci);1465free(ci); /* free the base struct as well */1466}1467}14681469/*1470* get_netscape_format()1471*1472* Formats a string for Netscape output file, w/o a newline at the end.1473* Function returns a char * to a formatted line. The caller is responsible1474* for freeing the returned pointer.1475*/1476static char *get_netscape_format(const struct Cookie *co)1477{1478return aprintf(1479"%s" /* httponly preamble */1480"%s%s\t" /* domain */1481"%s\t" /* tailmatch */1482"%s\t" /* path */1483"%s\t" /* secure */1484"%" FMT_OFF_T "\t" /* expires */1485"%s\t" /* name */1486"%s", /* value */1487co->httponly ? "#HttpOnly_" : "",1488/*1489* Make sure all domains are prefixed with a dot if they allow1490* tailmatching. This is Mozilla-style.1491*/1492(co->tailmatch && co->domain && co->domain[0] != '.') ? "." : "",1493co->domain ? co->domain : "unknown",1494co->tailmatch ? "TRUE" : "FALSE",1495co->path ? co->path : "/",1496co->secure ? "TRUE" : "FALSE",1497co->expires,1498co->name,1499co->value ? co->value : "");1500}15011502/*1503* cookie_output()1504*1505* Writes all internally known cookies to the specified file. Specify1506* "-" as filename to write to stdout.1507*1508* The function returns non-zero on write failure.1509*/1510static CURLcode cookie_output(struct Curl_easy *data,1511struct CookieInfo *ci,1512const char *filename)1513{1514FILE *out = NULL;1515bool use_stdout = FALSE;1516char *tempstore = NULL;1517CURLcode error = CURLE_OK;15181519if(!ci)1520/* no cookie engine alive */1521return CURLE_OK;15221523/* at first, remove expired cookies */1524remove_expired(ci);15251526if(!strcmp("-", filename)) {1527/* use stdout */1528out = stdout;1529use_stdout = TRUE;1530}1531else {1532error = Curl_fopen(data, filename, &out, &tempstore);1533if(error)1534goto error;1535}15361537fputs("# Netscape HTTP Cookie File\n"1538"# https://curl.se/docs/http-cookies.html\n"1539"# This file was generated by libcurl! Edit at your own risk.\n\n",1540out);15411542if(ci->numcookies) {1543unsigned int i;1544size_t nvalid = 0;1545struct Cookie **array;1546struct Curl_llist_node *n;15471548array = calloc(1, sizeof(struct Cookie *) * ci->numcookies);1549if(!array) {1550error = CURLE_OUT_OF_MEMORY;1551goto error;1552}15531554/* only sort the cookies with a domain property */1555for(i = 0; i < COOKIE_HASH_SIZE; i++) {1556for(n = Curl_llist_head(&ci->cookielist[i]); n;1557n = Curl_node_next(n)) {1558struct Cookie *co = Curl_node_elem(n);1559if(!co->domain)1560continue;1561array[nvalid++] = co;1562}1563}15641565qsort(array, nvalid, sizeof(struct Cookie *), cookie_sort_ct);15661567for(i = 0; i < nvalid; i++) {1568char *format_ptr = get_netscape_format(array[i]);1569if(!format_ptr) {1570free(array);1571error = CURLE_OUT_OF_MEMORY;1572goto error;1573}1574fprintf(out, "%s\n", format_ptr);1575free(format_ptr);1576}15771578free(array);1579}15801581if(!use_stdout) {1582fclose(out);1583out = NULL;1584if(tempstore && Curl_rename(tempstore, filename)) {1585unlink(tempstore);1586error = CURLE_WRITE_ERROR;1587goto error;1588}1589}15901591/*1592* If we reach here we have successfully written a cookie file so there is1593* no need to inspect the error, any error case should have jumped into the1594* error block below.1595*/1596free(tempstore);1597return CURLE_OK;15981599error:1600if(out && !use_stdout)1601fclose(out);1602free(tempstore);1603return error;1604}16051606static struct curl_slist *cookie_list(struct Curl_easy *data)1607{1608struct curl_slist *list = NULL;1609struct curl_slist *beg;1610unsigned int i;1611struct Curl_llist_node *n;16121613if(!data->cookies || (data->cookies->numcookies == 0))1614return NULL;16151616for(i = 0; i < COOKIE_HASH_SIZE; i++) {1617for(n = Curl_llist_head(&data->cookies->cookielist[i]); n;1618n = Curl_node_next(n)) {1619struct Cookie *c = Curl_node_elem(n);1620char *line;1621if(!c->domain)1622continue;1623line = get_netscape_format(c);1624if(!line) {1625curl_slist_free_all(list);1626return NULL;1627}1628beg = Curl_slist_append_nodup(list, line);1629if(!beg) {1630free(line);1631curl_slist_free_all(list);1632return NULL;1633}1634list = beg;1635}1636}16371638return list;1639}16401641struct curl_slist *Curl_cookie_list(struct Curl_easy *data)1642{1643struct curl_slist *list;1644Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);1645list = cookie_list(data);1646Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);1647return list;1648}16491650void Curl_flush_cookies(struct Curl_easy *data, bool cleanup)1651{1652CURLcode res;16531654if(data->set.str[STRING_COOKIEJAR]) {1655Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);16561657/* if we have a destination file for all the cookies to get dumped to */1658res = cookie_output(data, data->cookies, data->set.str[STRING_COOKIEJAR]);1659if(res)1660infof(data, "WARNING: failed to save cookies in %s: %s",1661data->set.str[STRING_COOKIEJAR], curl_easy_strerror(res));1662}1663else {1664Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);1665}16661667if(cleanup && (!data->share || (data->cookies != data->share->cookies))) {1668Curl_cookie_cleanup(data->cookies);1669data->cookies = NULL;1670}1671Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);1672}16731674#endif /* CURL_DISABLE_HTTP || CURL_DISABLE_COOKIES */167516761677