CVE-2026-86412: server-set cookie erases cookie jar
What the report claims
A remote server can set a cookie that curl writes to a Netscape cookie jar but cannot reload, causing cleanup to overwrite the jar with an empty header and delete cookies for unrelated domains.
Compare with curl's recorded outcome
Known vulnerability
A real vulnerability: CVE-2026-86412 server-set cookie erases cookie jar
Shown for comparison after analysis. The pipeline did not see this label.
ProveIt's finding
The code supports the reported flaw. A maintainer should review its impact and decide what to do next.
Why this finding?
- parse_cookie_header accepts 5000-byte headers (lib/cookie.c@curl-8_21_0:604-609) but get_netscape_format emits records with no length check (lib/cookie.c:1487-1511), allowing a 4999-byte record that the loader cannot reload (lib/cookie.c:1178 + lib/curl_get_line.c:56-63 effective limit 4998 bytes).
- cookie_load sets ci->running = TRUE unconditionally at line 1211 even after CURLE_TOO_LARGE, so cleanup calls cookie_output with an empty store.
- cookie_output atomically replaces the jar file (lib/cookie.c:1594), deleting all cookies including unrelated domains' cookies.
- The flaw is reachable by a remote unauthenticated server via Set-Cookie header, causes persistent DoS and cross-domain data corruption, and is not listed in the project's 'not security issues' policy.
How this analysis ran 5 claims · 19 citations verified
Checked against curl master on 2026-08-27 (b73a4f00b2a6). Test evaluation split. Citation verification checks that a referenced line was in the evidence; it does not prove the model interpreted it correctly.
Review a draft reply to the reporter
Edit and verify before sending.
Thank you for the detailed report and reproduction steps. The code confirms the inconsistency: parse_cookie_header accepts 5000-byte headers, get_netscape_format can write a 4999-byte record, but Curl_get_line's 5000-byte dynbuf (including NUL) only reloads 4998 bytes of payload. The unconditional ci->running = TRUE at lib/cookie.c:1211 lets cleanup overwrite the jar with an empty store. This is a valid security issue (remote DoS and persistent state corruption). We will address both the length mismatch and the running-flag-on-error.
Inspect all claims and source code 5 claims · 11 code excerpts
Report claims
A remote HTTP server can set a cookie that curl accepts and writes to a Netscape cookie jar but cannot reload, leading to cleanup overwriting the jar with an empty header and deleting cookies for unrelated domains.
Read the reasoning
The code confirms the inconsistency: parse_cookie_header accepts 5000-byte headers, get_netscape_format can emit a 4999-byte record (plus newline = 5000 bytes in file), but Curl_get_line's 5000-byte dynbuf (including NUL) can only reload 4998 bytes of payload plus newline. A 4999-byte record exceeds this. The unconditional ci->running = TRUE at line 1211 lets cleanup call cookie_output with an empty store, overwriting the jar.
The flaw occurs in the cookie handling code, specifically in `parse_cookie_header()`, `get_netscape_format()`, and `cookie_load()` functions in lib/cookie.c.
Read the reasoning
All three functions cited by the report exist in lib/cookie.c and contain the described logic.
An attacker-controlled HTTP server sets a cookie whose fully serialized Netscape record exceeds the loader's 5000-byte dynbuf limit, causing `CURLE_TOO_LARGE` before any connection is made.
Read the reasoning
Cookie loading happens in Curl_pretransfer before connecting. A record that serializes to 4999 bytes (5000 with newline) exceeds the loader's effective 4998-byte payload limit, causing CURLE_TOO_LARGE before any network I/O.
The failed load causes cleanup to replace the shared cookie jar with an empty header, permanently erasing cookies belonging to unrelated domains and causing availability loss and state corruption.
Read the reasoning
The failed load leaves ci->running = TRUE with an empty cookie store. Cleanup (via cookie_output) then writes an empty jar (header only), deleting all cookies including unrelated domains' cookies.
Steps to reproduce involve setting a victim cookie, contacting an attacker server to set a poisoned cookie that serializes to 4999 bytes, then reloading the jar as both input and output, resulting in `CURLE_TOO_LARGE` and jar overwrite.
Read the reasoning
The reproduction steps follow the code path: attacker cookie accepted by parser (≤5000 bytes), serialized to 4999 bytes, written to jar, then reload fails at 4999 bytes (exceeds 4998-byte reload limit), CURLE_TOO_LARGE returned, but running=TRUE causes cleanup to overwrite jar with empty store. The 4998-byte control works because it fits the reload limit.
Read the original report (excerpt)
Source code
| 1141 | static CURLcode cookie_load(struct Curl_easy *data, const char *file, |
| C2: cookie_load definition | |
| 1142 | struct CookieInfo *ci, int flags) |
| 1143 | { |
| 1144 | FILE *handle = NULL; |
| 1145 | CURLcode result = CURLE_OK; |
| 1146 | FILE *fp = NULL; |
| 1147 | DEBUGASSERT(ci); |
| 1148 | DEBUGASSERT(data); |
| 1149 | DEBUGASSERT(file); |
| 1150 | |
| 1151 | ci->newsession = !!(flags & COOKIE_NOSESSION); /* new session? */ |
| 1152 | ci->running = FALSE; /* this is not running, this is init */ |
| 1153 | |
| 1154 | if(file && *file) { |
| 1155 | if(!strcmp(file, "-")) |
| 1156 | fp = stdin; |
| 1157 | else { |
| 1158 | fp = curlx_fopen(file, "rb"); |
| 1159 | if(!fp) |
| 1160 | infof(data, "WARNING: failed to open cookie file \"%s\"", file); |
| 1161 | else { |
| 1162 | curlx_struct_stat stat; |
| 1163 | if((curlx_fstat(fileno(fp), &stat) != -1) && S_ISDIR(stat.st_mode)) { |
| 1164 | curlx_fclose(fp); |
| 1165 | fp = NULL; |
| 1166 | infof(data, "WARNING: cookie filename points to a directory: \"%s\"", |
| 1167 | file); |
| 1168 | } |
| 1169 | else |
| 1170 | handle = fp; |
| 1171 | } |
| 1172 | } |
| 1173 | } |
| 1174 | |
| 1175 | if(fp) { |
| 1176 | struct dynbuf buf; |
| 1177 | bool eof = FALSE; |
| 1178 | curlx_dyn_init(&buf, MAX_COOKIE_LINE); |
| C1: cookie_load initializes dynbuf with MAX_COOKIE_LINE (5000) limit | |
| C3: dynbuf limit of 5000 bytes in cookie_load | |
| C5: loader's dynbuf limit creates the one-byte boundary | |
| 1179 | do { |
| 1180 | result = Curl_get_line(&buf, fp, &eof); |
| 1181 | if(!result) { |
| 1182 | const char *lineptr = curlx_dyn_ptr(&buf); |
| 1183 | bool headerline = FALSE; |
| 1184 | if(checkprefix("Set-Cookie:", lineptr)) { |
| 1185 | /* This is a cookie line, get it! */ |
| 1186 | lineptr += 11; |
| 1187 | headerline = TRUE; |
| 1188 | curlx_str_passblanks(&lineptr); |
| 1189 | } |
| 1190 | |
| 1191 | result = Curl_cookie_add(data, ci, lineptr, NULL, NULL, |
| 1192 | (headerline ? COOKIE_HTTPHEADER : 0) | |
| 1193 | COOKIE_NOEXPIRE | COOKIE_SECURE | |
| 1194 | (flags & COOKIE_NOPSL)); |
| 1195 | /* File reading cookie failures are not propagated back to the |
| 1196 | caller because there is no way to do that */ |
| 1197 | } |
| 1198 | } while(!result && !eof); |
| 1199 | curlx_dyn_free(&buf); /* free the line buffer */ |
| 1200 | |
| 1201 | /* |
| 1202 | * Remove expired cookies from the hash. We must make sure to run this |
| 1203 | * after reading the file, and not on every cookie. |
| 1204 | */ |
| 1205 | remove_expired(ci); |
| 1206 | |
| 1207 | if(handle) |
| 1208 | curlx_fclose(handle); |
| 1209 | } |
| 1210 | data->state.cookie_engine = TRUE; |
| 1211 | ci->running = TRUE; /* now, we are running */ |
| C1: cookie_load sets ci->running = TRUE unconditionally, even after CURLE_TOO_LARGE | |
| C4: ci->running = TRUE set unconditionally after load error | |
| C5: running flag set even on error enables destructive cleanup | |
| 1212 | |
| 1213 | return result; |
| 1214 | } |
| 1215 | |
| 1216 | /* |
| 1217 | * Load cookies from all given cookie files (CURLOPT_COOKIEFILE). |
| 1218 | */ |
| 1219 | CURLcode Curl_cookie_loadfiles(struct Curl_easy *data, |
| 1220 | int flags) |
| 1221 | { |
| 1222 | CURLcode result = CURLE_OK; |
| 1223 | struct curl_slist *list = data->state.cookielist; |
| 1224 | if(list) { |
| 1225 | Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); |
| 1226 | if(!data->cookies) |
| 1227 | data->cookies = Curl_cookie_init(); |
| 1228 | if(!data->cookies) |
| 1229 | result = CURLE_OUT_OF_MEMORY; |
| 1230 | else { |
| 1231 | data->state.cookie_engine = TRUE; |
| 1232 | while(list) { |
| 1233 | result = cookie_load(data, list->data, data->cookies, flags); |
| 1234 | if(result) |
| 1235 | break; |
| 1236 | list = list->next; |
| 35 | CURLcode Curl_get_line(struct dynbuf *buf, FILE *input, bool *eof) |
| 36 | { |
| 37 | CURLcode result; |
| 38 | char buffer[128]; |
| 39 | curlx_dyn_reset(buf); |
| 40 | while(1) { |
| 41 | size_t rlen; |
| 42 | const char *b = fgets(buffer, sizeof(buffer), input); |
| 43 | if(!b && ferror(input)) |
| 44 | return CURLE_READ_ERROR; |
| 45 | |
| 46 | *eof = feof(input); |
| 47 | |
| 48 | rlen = b ? strlen(b) : 0; |
| 49 | if(rlen) { |
| 50 | result = curlx_dyn_addn(buf, b, rlen); |
| 51 | if(result) |
| 52 | /* too long line or out of memory */ |
| 53 | return result; |
| C3: Curl_get_line returns CURLE_TOO_LARGE when line exceeds dynbuf limit | |
| 54 | } |
| 55 | /* now check the full line */ |
| 56 | rlen = curlx_dyn_len(buf); |
| C1: Curl_get_line checks line length against dynbuf limit; limit includes NUL terminator and it appends newline on EOF, so max payload is 4998 bytes | |
| 57 | b = curlx_dyn_ptr(buf); |
| 58 | if(rlen && (b[rlen - 1] == '\n')) |
| 59 | /* LF at end of the line */ |
| 60 | return CURLE_OK; /* all good */ |
| 61 | if(*eof) |
| 62 | /* append a newline */ |
| 63 | return curlx_dyn_addn(buf, "\n", 1); |
| 64 | /* otherwise get next line to append */ |
| 65 | } |
| 66 | /* UNREACHABLE */ |
| 67 | } |
| 446 | CURLcode Curl_pretransfer(struct Curl_easy *data) |
| 447 | { |
| 448 | CURLcode result = CURLE_OK; |
| 449 | |
| 450 | /* Reset the retry count at the start of each request. |
| 451 | * If the retry count is not reset, when the connection drops, |
| 452 | * it will not enter the retry mechanism on CONN_MAX_RETRIES + 1 attempts |
| 453 | * and will immediately throw |
| 454 | * "Connection died, tried CONN_MAX_RETRIES times before giving up". |
| 455 | * By resetting it here, we ensure each new request starts fresh. */ |
| 456 | data->state.retrycount = 0; |
| 457 | |
| 458 | if(!data->set.str[STRING_SET_URL] && !data->set.uh) { |
| 459 | /* we cannot do anything without URL */ |
| 460 | failf(data, "No URL set"); |
| 461 | return CURLE_URL_MALFORMAT; |
| 462 | } |
| 463 | |
| 464 | /* CURLOPT_CURLU overrides CURLOPT_URL and the contents of the CURLU handle |
| 465 | is allowed to be changed by the user between transfers */ |
| 466 | if(data->set.uh) { |
| 467 | CURLUcode uc; |
| 468 | curlx_free(data->set.str[STRING_SET_URL]); |
| 469 | uc = curl_url_get(data->set.uh, |
| 470 | CURLUPART_URL, &data->set.str[STRING_SET_URL], 0); |
| 471 | if(uc) { |
| 472 | /* clear the pointer to not point to freed memory anymore */ |
| 473 | Curl_bufref_set(&data->state.url, NULL, 0, NULL); |
| 474 | failf(data, "No URL set"); |
| 475 | return CURLE_URL_MALFORMAT; |
| 476 | } |
| 477 | } |
| 478 | |
| 479 | Curl_bufref_set(&data->state.url, data->set.str[STRING_SET_URL], 0, NULL); |
| 480 | |
| 481 | if(data->set.postfields && data->set.set_resume_from) { |
| 482 | /* we cannot */ |
| 483 | failf(data, "cannot mix POSTFIELDS with RESUME_FROM"); |
| 484 | return CURLE_BAD_FUNCTION_ARGUMENT; |
| 485 | } |
| 486 | |
| 487 | data->state.prefer_ascii = data->set.prefer_ascii; |
| 488 | #ifdef CURL_LIST_ONLY_PROTOCOL |
| 489 | data->state.list_only = data->set.list_only; |
| 490 | #endif |
| 491 | data->state.httpreq = data->set.method; |
| 492 | |
| 493 | /* initial transfer request coming up, forget the initial origin |
| 494 | * from a previous perform() on this handle. */ |
| 495 | Curl_peer_unlink(&data->state.initial_origin); |
| 496 | Curl_peer_unlink(&data->state.origin); |
| 497 | data->state.requests = 0; |
| 498 | data->state.followlocation = 0; /* reset the location-follow counter */ |
| 499 | data->state.this_is_a_follow = FALSE; /* reset this */ |
| 500 | data->state.http_ignorecustom = FALSE; /* use custom HTTP method */ |
| 501 | data->state.errorbuf = FALSE; /* no error has occurred */ |
| 502 | #ifndef CURL_DISABLE_HTTP |
| 503 | Curl_http_neg_init(data, &data->state.http_neg); |
| 504 | #endif |
| 505 | data->state.authproblem = FALSE; |
| 506 | data->state.authhost.want = data->set.httpauth; |
| 507 | data->state.authproxy.want = data->set.proxyauth; |
| 508 | curlx_safefree(data->info.wouldredirect); |
| 509 | Curl_data_priority_clear_state(data); |
| 510 | if(data->set.http_auto_referer) |
| 511 | Curl_bufref_free(&data->state.referer); |
| 512 | if(data->set.str[STRING_SET_REFERER]) |
| 513 | Curl_bufref_set(&data->state.referer, data->set.str[STRING_SET_REFERER], |
| 514 | 0, NULL); |
| 515 | else |
| 516 | Curl_bufref_free(&data->state.referer); |
| 517 | |
| 518 | if(data->state.httpreq == HTTPREQ_PUT) |
| 519 | data->state.infilesize = data->set.filesize; |
| 520 | else if((data->state.httpreq != HTTPREQ_GET) && |
| 521 | (data->state.httpreq != HTTPREQ_HEAD)) { |
| 522 | data->state.infilesize = data->set.postfieldsize; |
| 523 | if(data->set.postfields && (data->state.infilesize == -1)) |
| 524 | data->state.infilesize = (curl_off_t)strlen(data->set.postfields); |
| 525 | } |
| 526 | else |
| 527 | data->state.infilesize = 0; |
| 528 | |
| 529 | /* If there is a list of cookie files to read, do it now! */ |
| C3: Curl_pretransfer calls Curl_cookie_loadfiles before any connection attempt | |
| 530 | result = Curl_cookie_loadfiles(data); |
| 531 | if(!result) |
| 532 | Curl_cookie_run(data); /* activate */ |
| 533 | |
| 534 | /* If there is a list of host pairs to deal with */ |
| 535 | if(!result && data->state.resolve) |
| 536 | result = Curl_loadhostpairs(data); |
| 537 | |
| 538 | if(!result) |
| 539 | /* If there is a list of hsts files to read */ |
| 540 | result = Curl_hsts_loadfiles(data); |
| 541 | |
| 542 | if(!result) { |
| 543 | /* Allow data->set.use_port to set which port to use. This needs to be |
| 544 | * disabled for example when we follow Location: headers to URLs using |
| 545 | * different ports! */ |
| 546 | data->state.allow_port = TRUE; |
| 547 | |
| 548 | #if defined(HAVE_SIGNAL) && defined(SIGPIPE) && !defined(MSG_NOSIGNAL) |
| 549 | /************************************************************* |
| 550 | * Tell signal handler to ignore SIGPIPE |
| 551 | *************************************************************/ |
| 552 | if(!data->set.no_signal) |
| 553 | data->state.prev_signal = signal(SIGPIPE, SIG_IGN); |
| 554 | #endif |
| 555 | |
| 556 | Curl_initinfo(data); /* reset session-specific information "variables" */ |
| 557 | Curl_pgrsResetTransferSizes(data); |
| 558 | Curl_pgrsStartNow(data); |
| 559 | |
| 560 | /* In case the handle is reused and an authentication method was picked |
| 561 | in the session we need to make sure we only use the one(s) we now |
| 562 | consider to be fine */ |
| 563 | data->state.authhost.picked &= data->state.authhost.want; |
| 564 | data->state.authproxy.picked &= data->state.authproxy.want; |
| 565 | |
| 566 | #ifndef CURL_DISABLE_FTP |
| 567 | data->state.wildcardmatch = data->set.wildcard_enabled; |
| 568 | if(data->state.wildcardmatch) { |
| 569 | struct WildcardData *wc; |
| 570 | if(!data->wildcard) { |
| 571 | data->wildcard = curlx_calloc(1, sizeof(struct WildcardData)); |
| 572 | if(!data->wildcard) |
| 573 | return CURLE_OUT_OF_MEMORY; |
| 574 | } |
| 575 | wc = data->wildcard; |
| 576 | if(wc->state < CURLWC_INIT) { |
| 577 | if(wc->ftpwc) |
| 578 | wc->dtor(wc->ftpwc); |
| 579 | curlx_safefree(wc->pattern); |
| 580 | curlx_safefree(wc->path); |
| 581 | Curl_wildcard_init(wc); /* init wildcard structures */ |
| 582 | } |
| 583 | } |
| 584 | #endif |
| 585 | result = Curl_hsts_loadcb(data, data->hsts); |
| 586 | } |
| 587 | |
| 588 | /* |
| 589 | * Set user-agent. Used for HTTP, but since we can attempt to tunnel |
| 590 | * anything through an HTTP proxy we cannot limit this based on protocol. |
| 591 | */ |
| 592 | if(!result && data->set.str[STRING_USERAGENT]) { |
| 593 | curlx_free(data->state.aptr.uagent); |
| 594 | data->state.aptr.uagent = |
| 595 | curl_maprintf("User-Agent: %s\r\n", data->set.str[STRING_USERAGENT]); |
| 596 | if(!data->state.aptr.uagent) |
| 597 | return CURLE_OUT_OF_MEMORY; |
| 598 | } |
| 599 | |
| 600 | data->req.headerbytecount = 0; |
| 601 | Curl_headers_cleanup(data); |
| 602 | return result; |
| 603 | } |
| 1521 | static CURLcode cookie_output(struct Curl_easy *data, |
| C1: cookie_output writes the store (empty after failed load) to the jar, overwriting it | |
| C4: cookie_output writes whatever is in the store; after failed load the store is empty | |
| 1522 | struct CookieInfo *ci, |
| 1523 | const char *filename) |
| 1524 | { |
| 1525 | FILE *out = NULL; |
| 1526 | bool use_stdout = FALSE; |
| 1527 | char *tempstore = NULL; |
| 1528 | CURLcode result = CURLE_OK; |
| 1529 | |
| 1530 | if(!ci) |
| 1531 | /* no cookie engine alive */ |
| 1532 | return CURLE_OK; |
| 1533 | |
| 1534 | /* at first, remove expired cookies */ |
| 1535 | remove_expired(ci); |
| 1536 | |
| 1537 | if(!strcmp("-", filename)) { |
| 1538 | /* use stdout */ |
| 1539 | out = stdout; |
| 1540 | use_stdout = TRUE; |
| 1541 | } |
| 1542 | else { |
| 1543 | result = Curl_fopen(data, filename, &out, &tempstore); |
| 1544 | if(result) |
| 1545 | goto error; |
| 1546 | } |
| 1547 | |
| 1548 | fputs("# Netscape HTTP Cookie File\n" |
| 1549 | "# https://curl.se/docs/http-cookies.html\n" |
| 1550 | "# This file was generated by libcurl! Edit at your own risk.\n\n", |
| 1551 | out); |
| 1552 | |
| 1553 | if(ci->numcookies) { |
| 1554 | unsigned int i; |
| 1555 | size_t nvalid = 0; |
| 1556 | struct Cookie **array; |
| 1557 | struct Curl_llist_node *n; |
| 1558 | |
| 1559 | array = curlx_calloc(1, sizeof(struct Cookie *) * ci->numcookies); |
| 1560 | if(!array) { |
| 1561 | result = CURLE_OUT_OF_MEMORY; |
| 1562 | goto error; |
| 1563 | } |
| 1564 | |
| 1565 | /* only sort the cookies with a domain property */ |
| 1566 | for(i = 0; i < COOKIE_HASH_SIZE; i++) { |
| 1567 | for(n = Curl_llist_head(&ci->cookielist[i]); n; n = Curl_node_next(n)) { |
| 1568 | struct Cookie *co = Curl_node_elem(n); |
| 1569 | if(!co->domain) |
| 1570 | continue; |
| 1571 | array[nvalid++] = co; |
| 1572 | } |
| 1573 | } |
| 1574 | |
| 1575 | qsort(array, nvalid, sizeof(struct Cookie *), cookie_sort_ct); |
| 1576 | |
| 1577 | for(i = 0; i < nvalid; i++) { |
| 1578 | char *format_ptr = get_netscape_format(array[i]); |
| 1579 | if(!format_ptr) { |
| 1580 | curlx_free(array); |
| 1581 | result = CURLE_OUT_OF_MEMORY; |
| 1582 | goto error; |
| 1583 | } |
| 1584 | curl_mfprintf(out, "%s\n", format_ptr); |
| 1585 | curlx_free(format_ptr); |
| 1586 | } |
| 1587 | |
| 1588 | curlx_free(array); |
| 1589 | } |
| 1590 | |
| 1591 | if(!use_stdout) { |
| 1592 | curlx_fclose(out); |
| 1593 | out = NULL; |
| 1594 | if(tempstore && curlx_rename(tempstore, filename)) { |
| C4: cookie_output atomically renames tempstore over the original jar file | |
| 1595 | result = CURLE_WRITE_ERROR; |
| 1596 | goto error; |
| 1597 | } |
| 1598 | } |
| 1599 | |
| 1600 | /* |
| 1601 | * If we reach here we have successfully written a cookie file so there is |
| 1602 | * no need to inspect the error, any error case should have jumped into the |
| 1603 | * error block below. |
| 1604 | */ |
| 1605 | curlx_free(tempstore); |
| 1606 | return CURLE_OK; |
| 1607 | |
| 1608 | error: |
| 1609 | if(out && !use_stdout) |
| 1610 | curlx_fclose(out); |
| 1611 | if(tempstore) { |
| 1612 | unlink(tempstore); |
| 1613 | curlx_free(tempstore); |
| 1614 | } |
| 1615 | return result; |
| 1616 | } |
| 1487 | static char *get_netscape_format(const struct Cookie *co) |
| C1: get_netscape_format serializes cookies with no length check on the output record | |
| C2: get_netscape_format definition | |
| C5: get_netscape_format can produce records up to the parser's limit | |
| 1488 | { |
| 1489 | return curl_maprintf( |
| 1490 | "%s" /* httponly preamble */ |
| 1491 | "%s%s\t" /* domain */ |
| 1492 | "%s\t" /* tailmatch */ |
| 1493 | "%s\t" /* path */ |
| 1494 | "%s\t" /* secure */ |
| 1495 | "%" FMT_OFF_T "\t" /* expires */ |
| 1496 | "%s\t" /* name */ |
| 1497 | "%s", /* value */ |
| 1498 | co->httponly ? "#HttpOnly_" : "", |
| 1499 | /* |
| 1500 | * Make sure all domains are prefixed with a dot if they allow |
| 1501 | * tailmatching. This is Mozilla-style. |
| 1502 | */ |
| 1503 | (co->tailmatch && co->domain && co->domain[0] != '.') ? "." : "", |
| 1504 | co->domain ? co->domain : "unknown", |
| 1505 | co->tailmatch ? "TRUE" : "FALSE", |
| 1506 | co->path ? co->path : "/", |
| 1507 | co->secure ? "TRUE" : "FALSE", |
| 1508 | co->expires, |
| 1509 | co->name, |
| 1510 | co->value ? co->value : ""); |
| 1511 | } |
| 590 | static CURLcode |
| C2: parse_cookie_header definition | |
| 591 | parse_cookie_header(struct Curl_easy *data, |
| 592 | struct Cookie *co, |
| 593 | const struct CookieInfo *ci, |
| 594 | bool *okay, /* if the cookie was fine */ |
| 595 | const char *ptr, /* the header */ |
| 596 | const char *domain, /* default domain */ |
| 597 | /* full path used when this cookie is set */ |
| 598 | const char *path, |
| 599 | bool secure_origin) |
| 600 | { |
| 601 | /* This line was read off an HTTP-header */ |
| 602 | time_t now = 0; |
| 603 | size_t linelength = strlen(ptr); |
| 604 | CURLcode result = CURLE_OK; |
| C1: parse_cookie_header accepts lines up to MAX_COOKIE_LINE (5000) bytes | |
| C5: parse_cookie_header accepts 5000-byte header lines | |
| 605 | struct Curl_str cookie[COOKIE_PIECES]; |
| 606 | *okay = FALSE; |
| 607 | if(linelength > MAX_COOKIE_LINE) |
| 608 | /* discard overly long lines at once */ |
| 609 | return CURLE_OK; |
| 610 | |
| 611 | /* memset instead of initializer because gcc 4.8.1 is silly */ |
| 612 | memset(cookie, 0, sizeof(cookie)); |
| 613 | do { |
| 614 | struct Curl_str name; |
| 615 | |
| 616 | /* we have a <name>=<value> pair or a stand-alone word here */ |
| 617 | if(!curlx_str_cspn(&ptr, &name, ";\t\r\n=")) { |
| 618 | struct Curl_str val; |
| 619 | bool sep = FALSE; |
| 620 | curlx_str_trimblanks(&name); |
| 621 | |
| 622 | if(invalid_octets(curlx_str(&name), curlx_strlen(&name))) { |
| 623 | infof(data, "invalid octets in name, cookie dropped"); |
| 624 | return CURLE_OK; |
| 625 | } |
| 626 | |
| 627 | if(!curlx_str_single(&ptr, '=')) { |
| 628 | sep = TRUE; /* a '=' was used */ |
| 629 | if(!curlx_str_cspn(&ptr, &val, ";\r\n")) |
| 630 | curlx_str_trimblanks(&val); |
| 631 | |
| 632 | if(invalid_octets(curlx_str(&val), curlx_strlen(&val))) { |
| 633 | infof(data, "invalid octets in value, cookie dropped"); |
| 634 | return CURLE_OK; |
| 635 | } |
| 636 | } |
| 637 | else |
| 638 | curlx_str_init(&val); |
| 639 | |
| 640 | if(!curlx_strlen(&cookie[COOKIE_NAME])) { |
| 641 | if(!parse_first_pair(data, co, cookie, &name, &val, sep)) |
| 642 | return CURLE_OK; |
| 643 | } |
| 644 | else if(!sep) { |
| 645 | if(!parse_flag(data, co, ci, &name, secure_origin)) |
| 646 | return CURLE_OK; |
| 647 | } |
| 648 | else if(curlx_str_casecompare(&name, "path")) |
| 649 | cookie[COOKIE_PATH] = val; |
| 650 | else if(curlx_str_casecompare(&name, "domain") && curlx_strlen(&val)) { |
| 651 | if(!parse_domain(data, co, &cookie[COOKIE_DOMAIN], &val, &domain)) |
| 652 | return CURLE_OK; |
| 653 | } |
| 654 | else if(curlx_str_casecompare(&name, "max-age") && curlx_strlen(&val)) |
| 655 | parse_maxage(co, &val, &now); |
| 656 | else if(curlx_str_casecompare(&name, "expires") && curlx_strlen(&val)) |
| 657 | parse_expires(co, &val, &now); |
| 658 | } |
| 659 | } while(!curlx_str_single(&ptr, ';')); |
| 660 | |
| 661 | if(curlx_strlen(&cookie[COOKIE_NAME])) { |
| 662 | /* the header was fine, now store the data */ |
| 663 | result = storecookie(co, &cookie[0], path, domain); |
| 664 | if(!result) |
| 665 | *okay = TRUE; |
| 666 | } |
| 667 | return result; |
| 668 | } |
Show 151 lines (not cited by the judge)
| 442 | CURLcode Curl_pretransfer(struct Curl_easy *data) |
| 443 | { |
| 444 | CURLcode result = CURLE_OK; |
| 445 | |
| 446 | /* Reset the retry count at the start of each request. |
| 447 | * If the retry count is not reset, when the connection drops, |
| 448 | * it will not enter the retry mechanism on CONN_MAX_RETRIES + 1 attempts |
| 449 | * and will immediately throw |
| 450 | * "Connection died, tried CONN_MAX_RETRIES times before giving up". |
| 451 | * By resetting it here, we ensure each new request starts fresh. */ |
| 452 | data->state.retrycount = 0; |
| 453 | |
| 454 | if(!CURL_EASY_STR(data, STRING_SET_URL) && !data->set.uh) { |
| 455 | /* we cannot do anything without URL */ |
| 456 | failf(data, "No URL set"); |
| 457 | return CURLE_URL_MALFORMAT; |
| 458 | } |
| 459 | |
| 460 | /* CURLOPT_CURLU overrides CURLOPT_URL and the contents of the CURLU handle |
| 461 | is allowed to be changed by the user between transfers */ |
| 462 | if(data->set.uh) { |
| 463 | char *url = NULL; |
| 464 | CURLUcode uc; |
| 465 | uc = curl_url_get(data->set.uh, CURLUPART_URL, &url, 0); |
| 466 | if(uc) { |
| 467 | /* clear the pointer to not point to freed memory anymore */ |
| 468 | Curl_bufref_set(&data->state.url, NULL, 0, NULL); |
| 469 | failf(data, "No URL set"); |
| 470 | return CURLE_URL_MALFORMAT; |
| 471 | } |
| 472 | result = CURL_EASY_STR_SETN(data, STRING_SET_URL, url); |
| 473 | if(result) |
| 474 | return result; |
| 475 | } |
| 476 | |
| 477 | Curl_bufref_set(&data->state.url, CURL_EASY_STR(data, STRING_SET_URL), |
| 478 | 0, NULL); |
| 479 | |
| 480 | if(data->set.postfields && data->set.set_resume_from) { |
| 481 | /* we cannot */ |
| 482 | failf(data, "cannot mix POSTFIELDS with RESUME_FROM"); |
| 483 | return CURLE_BAD_FUNCTION_ARGUMENT; |
| 484 | } |
| 485 | |
| 486 | data->state.prefer_ascii = data->set.prefer_ascii; |
| 487 | #ifdef CURL_LIST_ONLY_PROTOCOL |
| 488 | data->state.list_only = data->set.list_only; |
| 489 | #endif |
| 490 | data->state.httpreq = data->set.method; |
| 491 | |
| 492 | /* initial transfer request coming up, forget the initial origin |
| 493 | * from a previous perform() on this handle. */ |
| 494 | Curl_peer_unlink(&data->state.initial_origin); |
| 495 | Curl_peer_unlink(&data->state.origin); |
| 496 | data->state.requests = 0; |
| 497 | data->state.followlocation = 0; /* reset the location-follow counter */ |
| 498 | data->state.this_is_a_follow = FALSE; /* reset this */ |
| 499 | data->state.http_ignorecustom = FALSE; /* use custom HTTP method */ |
| 500 | data->state.errorbuf = FALSE; /* no error has occurred */ |
| 501 | #ifndef CURL_DISABLE_HTTP |
| 502 | Curl_http_neg_init(data, &data->state.http_neg); |
| 503 | #endif |
| 504 | data->state.authproblem = FALSE; |
| 505 | data->state.authhost.want = data->set.httpauth; |
| 506 | data->state.authproxy.want = data->set.proxyauth; |
| 507 | curlx_safefree(data->info.wouldredirect); |
| 508 | Curl_data_priority_clear_state(data); |
| 509 | if(data->set.http_auto_referer) |
| 510 | Curl_bufref_free(&data->state.referer); |
| 511 | if(CURL_EASY_STR(data, STRING_SET_REFERER)) |
| 512 | Curl_bufref_set(&data->state.referer, |
| 513 | CURL_EASY_STR(data, STRING_SET_REFERER), 0, NULL); |
| 514 | else |
| 515 | Curl_bufref_free(&data->state.referer); |
| 516 | |
| 517 | if(data->state.httpreq == HTTPREQ_PUT) |
| 518 | data->state.infilesize = data->set.filesize; |
| 519 | else if((data->state.httpreq != HTTPREQ_GET) && |
| 520 | (data->state.httpreq != HTTPREQ_HEAD)) { |
| 521 | data->state.infilesize = data->set.postfieldsize; |
| 522 | if(data->set.postfields && (data->state.infilesize == -1)) |
| 523 | data->state.infilesize = (curl_off_t)strlen(data->set.postfields); |
| 524 | } |
| 525 | else |
| 526 | data->state.infilesize = 0; |
| 527 | |
| 528 | /* If there is a list of cookie files to read, do it now! */ |
| 529 | result = Curl_cookie_loadfiles(data, |
| 530 | data->set.cookiesession ? |
| 531 | COOKIE_NOSESSION : 0); |
| 532 | if(!result) |
| 533 | Curl_cookie_run(data); /* activate */ |
| 534 | |
| 535 | /* If there is a list of host pairs to deal with */ |
| 536 | if(!result && data->state.resolve) |
| 537 | result = Curl_loadhostpairs(data); |
| 538 | |
| 539 | if(!result) |
| 540 | /* If there is a list of hsts files to read */ |
| 541 | result = Curl_hsts_loadfiles(data); |
| 542 | |
| 543 | if(!result) { |
| 544 | /* Allow data->set.use_port to set which port to use. This needs to be |
| 545 | * disabled for example when we follow Location: headers to URLs using |
| 546 | * different ports! */ |
| 547 | data->state.allow_port = TRUE; |
| 548 | |
| 549 | #if defined(HAVE_SIGNAL) && defined(SIGPIPE) && !defined(MSG_NOSIGNAL) |
| 550 | /************************************************************* |
| 551 | * Tell signal handler to ignore SIGPIPE |
| 552 | *************************************************************/ |
| 553 | if(!data->set.no_signal) |
| 554 | data->state.prev_signal = signal(SIGPIPE, SIG_IGN); |
| 555 | #endif |
| 556 | |
| 557 | Curl_initinfo(data); /* reset session-specific information "variables" */ |
| 558 | Curl_pgrsResetTransferSizes(data); |
| 559 | Curl_pgrsStart(data, NULL); |
| 560 | |
| 561 | /* In case the handle is reused and an authentication method was picked |
| 562 | in the session we need to make sure we only use the one(s) we now |
| 563 | consider to be fine */ |
| 564 | data->state.authhost.picked &= data->state.authhost.want; |
| 565 | data->state.authproxy.picked &= data->state.authproxy.want; |
| 566 | |
| 567 | #ifndef CURL_DISABLE_FTP |
| 568 | data->state.wildcardmatch = data->set.wildcard_enabled; |
| 569 | if(data->state.wildcardmatch) { |
| 570 | struct WildcardData *wc; |
| 571 | if(!data->wildcard) { |
| 572 | data->wildcard = curlx_calloc(1, sizeof(struct WildcardData)); |
| 573 | if(!data->wildcard) |
| 574 | return CURLE_OUT_OF_MEMORY; |
| 575 | } |
| 576 | wc = data->wildcard; |
| 577 | if(wc->state < CURLWC_INIT) { |
| 578 | if(wc->ftpwc) |
| 579 | wc->dtor(wc->ftpwc); |
| 580 | curlx_safefree(wc->pattern); |
| 581 | curlx_safefree(wc->path); |
| 582 | Curl_wildcard_init(wc); /* init wildcard structures */ |
| 583 | } |
| 584 | } |
| 585 | #endif |
| 586 | result = Curl_hsts_loadcb(data, data->hsts); |
| 587 | } |
| 588 | |
| 589 | data->req.headerbytecount = 0; |
| 590 | Curl_headers_cleanup(data); |
| 591 | return result; |
| 592 | } |
Show 1 lines (not cited by the judge)
| 81 | #define MAX_COOKIE_LINE 5000 |
Show 72 lines (not cited by the judge)
| 1129 | static CURLcode cookie_load(struct Curl_easy *data, const char *file, |
| 1130 | struct CookieInfo *ci, bool newsession) |
| 1131 | { |
| 1132 | FILE *handle = NULL; |
| 1133 | CURLcode result = CURLE_OK; |
| 1134 | FILE *fp = NULL; |
| 1135 | DEBUGASSERT(ci); |
| 1136 | DEBUGASSERT(data); |
| 1137 | DEBUGASSERT(file); |
| 1138 | |
| 1139 | ci->newsession = newsession; /* new session? */ |
| 1140 | ci->running = FALSE; /* this is not running, this is init */ |
| 1141 | |
| 1142 | if(file && *file) { |
| 1143 | if(!strcmp(file, "-")) |
| 1144 | fp = stdin; |
| 1145 | else { |
| 1146 | fp = curlx_fopen(file, "rb"); |
| 1147 | if(!fp) |
| 1148 | infof(data, "WARNING: failed to open cookie file \"%s\"", file); |
| 1149 | else { |
| 1150 | curlx_struct_stat stat; |
| 1151 | if((curlx_fstat(fileno(fp), &stat) != -1) && S_ISDIR(stat.st_mode)) { |
| 1152 | curlx_fclose(fp); |
| 1153 | fp = NULL; |
| 1154 | infof(data, "WARNING: cookie filename points to a directory: \"%s\"", |
| 1155 | file); |
| 1156 | } |
| 1157 | else |
| 1158 | handle = fp; |
| 1159 | } |
| 1160 | } |
| 1161 | } |
| 1162 | |
| 1163 | if(fp) { |
| 1164 | struct dynbuf buf; |
| 1165 | bool eof = FALSE; |
| 1166 | curlx_dyn_init(&buf, MAX_COOKIE_LINE); |
| 1167 | do { |
| 1168 | result = Curl_get_line(&buf, fp, &eof); |
| 1169 | if(!result) { |
| 1170 | const char *lineptr = curlx_dyn_ptr(&buf); |
| 1171 | bool headerline = FALSE; |
| 1172 | if(checkprefix("Set-Cookie:", lineptr)) { |
| 1173 | /* This is a cookie line, get it! */ |
| 1174 | lineptr += 11; |
| 1175 | headerline = TRUE; |
| 1176 | curlx_str_passblanks(&lineptr); |
| 1177 | } |
| 1178 | |
| 1179 | result = Curl_cookie_add(data, ci, headerline, TRUE, lineptr, NULL, |
| 1180 | NULL, TRUE); |
| 1181 | /* File reading cookie failures are not propagated back to the |
| 1182 | caller because there is no way to do that */ |
| 1183 | } |
| 1184 | } while(!result && !eof); |
| 1185 | curlx_dyn_free(&buf); /* free the line buffer */ |
| 1186 | |
| 1187 | /* |
| 1188 | * Remove expired cookies from the hash. We must make sure to run this |
| 1189 | * after reading the file, and not on every cookie. |
| 1190 | */ |
| 1191 | remove_expired(ci); |
| 1192 | |
| 1193 | if(handle) |
| 1194 | curlx_fclose(handle); |
| 1195 | } |
| 1196 | data->state.cookie_engine = TRUE; |
| 1197 | ci->running = TRUE; /* now, we are running */ |
| 1198 | |
| 1199 | return result; |
| 1200 | } |
Show 9 lines (not cited by the judge)
| 1184 | char *curl_maprintf(const char *format, ...) |
| 1185 | { |
| 1186 | va_list args; |
| 1187 | char *s; |
| 1188 | va_start(args, format); |
| 1189 | s = curl_mvaprintf(format, args); |
| 1190 | va_end(args); |
| 1191 | return s; |
| 1192 | } |
Show 79 lines (not cited by the judge)
| 587 | static CURLcode |
| 588 | parse_cookie_header(struct Curl_easy *data, |
| 589 | struct Cookie *co, |
| 590 | const struct CookieInfo *ci, |
| 591 | bool *okay, /* if the cookie was fine */ |
| 592 | const char *ptr, /* the header */ |
| 593 | const char *domain, /* default domain */ |
| 594 | /* full path used when this cookie is set */ |
| 595 | const char *path, |
| 596 | bool secure_origin) |
| 597 | { |
| 598 | /* This line was read off an HTTP-header */ |
| 599 | time_t now = 0; |
| 600 | size_t linelength = strlen(ptr); |
| 601 | CURLcode result = CURLE_OK; |
| 602 | struct Curl_str cookie[COOKIE_PIECES]; |
| 603 | *okay = FALSE; |
| 604 | if(linelength > MAX_COOKIE_LINE) |
| 605 | /* discard overly long lines at once */ |
| 606 | return CURLE_OK; |
| 607 | |
| 608 | /* memset instead of initializer because gcc 4.8.1 is silly */ |
| 609 | memset(cookie, 0, sizeof(cookie)); |
| 610 | do { |
| 611 | struct Curl_str name; |
| 612 | |
| 613 | /* we have a <name>=<value> pair or a stand-alone word here */ |
| 614 | if(!curlx_str_cspn(&ptr, &name, ";\r\n=")) { |
| 615 | struct Curl_str val; |
| 616 | bool sep = FALSE; |
| 617 | curlx_str_trimblanks(&name); |
| 618 | |
| 619 | if(invalid_octets(curlx_str(&name), curlx_strlen(&name))) { |
| 620 | infof(data, "invalid octets in name, cookie dropped"); |
| 621 | return CURLE_OK; |
| 622 | } |
| 623 | |
| 624 | if(!curlx_str_single(&ptr, '=')) { |
| 625 | sep = TRUE; /* a '=' was used */ |
| 626 | if(!curlx_str_cspn(&ptr, &val, ";\r\n")) |
| 627 | curlx_str_trimblanks(&val); |
| 628 | |
| 629 | if(invalid_octets(curlx_str(&val), curlx_strlen(&val))) { |
| 630 | infof(data, "invalid octets in value, cookie dropped"); |
| 631 | return CURLE_OK; |
| 632 | } |
| 633 | } |
| 634 | else |
| 635 | curlx_str_init(&val); |
| 636 | |
| 637 | if(!curlx_strlen(&cookie[COOKIE_NAME])) { |
| 638 | if(!parse_first_pair(data, co, cookie, &name, &val, sep)) |
| 639 | return CURLE_OK; |
| 640 | } |
| 641 | else if(!sep) { |
| 642 | if(!parse_flag(data, co, ci, &name, secure_origin)) |
| 643 | return CURLE_OK; |
| 644 | } |
| 645 | else if(curlx_str_casecompare(&name, "path")) |
| 646 | cookie[COOKIE_PATH] = val; |
| 647 | else if(curlx_str_casecompare(&name, "domain") && curlx_strlen(&val)) { |
| 648 | if(!parse_domain(data, co, &cookie[COOKIE_DOMAIN], &val, &domain)) |
| 649 | return CURLE_OK; |
| 650 | } |
| 651 | else if(curlx_str_casecompare(&name, "max-age") && curlx_strlen(&val)) |
| 652 | parse_maxage(co, &val, &now); |
| 653 | else if(curlx_str_casecompare(&name, "expires") && curlx_strlen(&val)) |
| 654 | parse_expires(co, &val, &now); |
| 655 | } |
| 656 | } while(!curlx_str_single(&ptr, ';')); |
| 657 | |
| 658 | if(curlx_strlen(&cookie[COOKIE_NAME])) { |
| 659 | /* the header was fine, now store the data */ |
| 660 | result = storecookie(co, &cookie[0], path, domain); |
| 661 | if(!result) |
| 662 | *okay = TRUE; |
| 663 | } |
| 664 | return result; |
| 665 | } |