parsedate: make Curl_getdate_capped able to return epoch

By returning error separately on parse errors and avoiding magic
numbers, this function can now return 0 or -1 as proper dates when such
a date string is provided.

Closes #18445
This commit is contained in:
Daniel Stenberg 2025-09-01 16:36:53 +02:00
parent 4d040c71d7
commit f8e6e11725
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
9 changed files with 50 additions and 68 deletions

View file

@ -187,13 +187,13 @@ static CURLcode altsvc_add(struct altsvcinfo *asi, const char *line)
else { else {
struct altsvc *as; struct altsvc *as;
char dbuf[MAX_ALTSVC_DATELEN + 1]; char dbuf[MAX_ALTSVC_DATELEN + 1];
time_t expires; time_t expires = 0;
/* The date parser works on a null-terminated string. The maximum length /* The date parser works on a null-terminated string. The maximum length
is upheld by curlx_str_quotedword(). */ is upheld by curlx_str_quotedword(). */
memcpy(dbuf, curlx_str(&date), curlx_strlen(&date)); memcpy(dbuf, curlx_str(&date), curlx_strlen(&date));
dbuf[curlx_strlen(&date)] = 0; dbuf[curlx_strlen(&date)] = 0;
expires = Curl_getdate_capped(dbuf); Curl_getdate_capped(dbuf, &expires);
as = altsvc_create(&srchost, &dsthost, &srcalpn, &dstalpn, as = altsvc_create(&srchost, &dsthost, &srcalpn, &dstalpn,
(size_t)srcport, (size_t)dstport); (size_t)srcport, (size_t)dstport);
if(as) { if(as) {

View file

@ -107,7 +107,7 @@ static void strstore(char **str, const char *newstr, size_t len);
*/ */
static void cap_expires(time_t now, struct Cookie *co) static void cap_expires(time_t now, struct Cookie *co)
{ {
if((TIME_T_MAX - COOKIES_MAXAGE - 30) > now) { if(co->expires && (TIME_T_MAX - COOKIES_MAXAGE - 30) > now) {
timediff_t cap = now + COOKIES_MAXAGE; timediff_t cap = now + COOKIES_MAXAGE;
if(co->expires > cap) { if(co->expires > cap) {
cap += 30; cap += 30;
@ -388,17 +388,17 @@ static void remove_expired(struct CookieInfo *ci)
for(n = Curl_llist_head(&ci->cookielist[i]); n; n = e) { for(n = Curl_llist_head(&ci->cookielist[i]); n; n = e) {
co = Curl_node_elem(n); co = Curl_node_elem(n);
e = Curl_node_next(n); e = Curl_node_next(n);
if(co->expires && co->expires < now) { if(co->expires) {
if(co->expires < now) {
Curl_node_remove(n); Curl_node_remove(n);
freecookie(co); freecookie(co);
ci->numcookies--; ci->numcookies--;
} }
else { else if(co->expires < ci->next_expiration)
/* /*
* If this cookie has an expiration timestamp earlier than what we * If this cookie has an expiration timestamp earlier than what we
* have seen so far then record it for the next round of expirations. * have seen so far then record it for the next round of expirations.
*/ */
if(co->expires && co->expires < ci->next_expiration)
ci->next_expiration = co->expires; ci->next_expiration = co->expires;
} }
} }
@ -666,7 +666,6 @@ parse_cookie_header(struct Curl_easy *data,
if(*maxage == '\"') if(*maxage == '\"')
maxage++; maxage++;
rc = curlx_str_number(&maxage, &co->expires, CURL_OFF_T_MAX); rc = curlx_str_number(&maxage, &co->expires, CURL_OFF_T_MAX);
switch(rc) { switch(rc) {
case STRE_OVERFLOW: case STRE_OVERFLOW:
/* overflow, used max value */ /* overflow, used max value */
@ -678,8 +677,7 @@ parse_cookie_header(struct Curl_easy *data,
break; break;
case STRE_OK: case STRE_OK:
if(!co->expires) if(!co->expires)
/* already expired */ co->expires = 1; /* expire now */
co->expires = 1;
else if(CURL_OFF_T_MAX - now < co->expires) else if(CURL_OFF_T_MAX - now < co->expires)
/* would overflow */ /* would overflow */
co->expires = CURL_OFF_T_MAX; co->expires = CURL_OFF_T_MAX;
@ -698,18 +696,15 @@ parse_cookie_header(struct Curl_easy *data,
* will be treated as a session cookie * will be treated as a session cookie
*/ */
char dbuf[MAX_DATE_LENGTH + 1]; char dbuf[MAX_DATE_LENGTH + 1];
time_t date = 0;
memcpy(dbuf, curlx_str(&val), curlx_strlen(&val)); memcpy(dbuf, curlx_str(&val), curlx_strlen(&val));
dbuf[curlx_strlen(&val)] = 0; dbuf[curlx_strlen(&val)] = 0;
co->expires = Curl_getdate_capped(dbuf); if(!Curl_getdate_capped(dbuf, &date)) {
if(!date)
/* date++;
* Session cookies have expires set to 0 so if we get that back co->expires = (curl_off_t)date;
* from the date parser let's add a second to make it a }
* non-session cookie else
*/
if(co->expires == 0)
co->expires = 1;
else if(co->expires < 0)
co->expires = 0; co->expires = 0;
cap_expires(now, co); cap_expires(now, co);
} }
@ -1103,7 +1098,7 @@ Curl_cookie_add(struct Curl_easy *data,
if(!ci->running && /* read from a file */ if(!ci->running && /* read from a file */
ci->newsession && /* clean session cookies */ ci->newsession && /* clean session cookies */
!co->expires) /* this is a session cookie since it does not expire */ !co->expires) /* this is a session cookie */
goto fail; goto fail;
co->livecookie = ci->running; co->livecookie = ci->running;

View file

@ -2102,6 +2102,7 @@ static CURLcode ftp_state_mdtm_resp(struct Curl_easy *data,
int year, month, day, hour, minute, second; int year, month, day, hour, minute, second;
struct pingpong *pp = &ftpc->pp; struct pingpong *pp = &ftpc->pp;
char *resp = curlx_dyn_ptr(&pp->recvbuf) + 4; char *resp = curlx_dyn_ptr(&pp->recvbuf) + 4;
bool showtime = FALSE;
if(ftp_213_date(resp, &year, &month, &day, &hour, &minute, &second)) { if(ftp_213_date(resp, &year, &month, &day, &hour, &minute, &second)) {
/* we have a time, reformat it */ /* we have a time, reformat it */
char timebuf[24]; char timebuf[24];
@ -2109,7 +2110,8 @@ static CURLcode ftp_state_mdtm_resp(struct Curl_easy *data,
"%04d%02d%02d %02d:%02d:%02d GMT", "%04d%02d%02d %02d:%02d:%02d GMT",
year, month, day, hour, minute, second); year, month, day, hour, minute, second);
/* now, convert this into a time() value: */ /* now, convert this into a time() value: */
data->info.filetime = Curl_getdate_capped(timebuf); if(!Curl_getdate_capped(timebuf, &data->info.filetime))
showtime = TRUE;
} }
#ifdef CURL_FTP_HTTPSTYLE_HEAD #ifdef CURL_FTP_HTTPSTYLE_HEAD
@ -2122,10 +2124,8 @@ static CURLcode ftp_state_mdtm_resp(struct Curl_easy *data,
warning: comparison of unsigned expression in '>= 0' is always true */ warning: comparison of unsigned expression in '>= 0' is always true */
#pragma GCC diagnostic ignored "-Wtype-limits" #pragma GCC diagnostic ignored "-Wtype-limits"
#endif #endif
if(data->req.no_body && if(data->req.no_body && ftpc->file &&
ftpc->file && data->set.get_filetime && showtime) {
data->set.get_filetime &&
(data->info.filetime >= 0) ) {
#if defined(__GNUC__) && (defined(__DJGPP__) || defined(__AMIGA__)) #if defined(__GNUC__) && (defined(__DJGPP__) || defined(__AMIGA__))
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
#endif #endif

View file

@ -426,7 +426,7 @@ static CURLcode hsts_add(struct hsts *h, const char *line)
bool subdomain = FALSE; bool subdomain = FALSE;
struct stsentry *e; struct stsentry *e;
char dbuf[MAX_HSTS_DATELEN + 1]; char dbuf[MAX_HSTS_DATELEN + 1];
time_t expires; time_t expires = 0;
const char *hp = curlx_str(&host); const char *hp = curlx_str(&host);
/* The date parser works on a null-terminated string. The maximum length /* The date parser works on a null-terminated string. The maximum length
@ -434,8 +434,10 @@ static CURLcode hsts_add(struct hsts *h, const char *line)
memcpy(dbuf, curlx_str(&date), curlx_strlen(&date)); memcpy(dbuf, curlx_str(&date), curlx_strlen(&date));
dbuf[curlx_strlen(&date)] = 0; dbuf[curlx_strlen(&date)] = 0;
expires = strcmp(dbuf, UNLIMITED) ? Curl_getdate_capped(dbuf) : if(!strcmp(dbuf, UNLIMITED))
TIME_T_MAX; expires = TIME_T_MAX;
else
Curl_getdate_capped(dbuf, &expires);
if(hp[0] == '.') { if(hp[0] == '.') {
curlx_str_nudge(&host, 1); curlx_str_nudge(&host, 1);
@ -478,14 +480,14 @@ static CURLcode hsts_pull(struct Curl_easy *data, struct hsts *h)
e.name[0] = 0; /* just to make it clean */ e.name[0] = 0; /* just to make it clean */
sc = data->set.hsts_read(data, &e, data->set.hsts_read_userp); sc = data->set.hsts_read(data, &e, data->set.hsts_read_userp);
if(sc == CURLSTS_OK) { if(sc == CURLSTS_OK) {
time_t expires; time_t expires = 0;
CURLcode result; CURLcode result;
DEBUGASSERT(e.name[0]); DEBUGASSERT(e.name[0]);
if(!e.name[0]) if(!e.name[0])
/* bail out if no name was stored */ /* bail out if no name was stored */
return CURLE_BAD_FUNCTION_ARGUMENT; return CURLE_BAD_FUNCTION_ARGUMENT;
if(e.expire[0]) if(e.expire[0])
expires = Curl_getdate_capped(e.expire); Curl_getdate_capped(e.expire, &expires);
else else
expires = TIME_T_MAX; /* the end of time */ expires = TIME_T_MAX; /* the end of time */
result = hsts_create(h, e.name, strlen(e.name), result = hsts_create(h, e.name, strlen(e.name),

View file

@ -3196,7 +3196,8 @@ static CURLcode http_header_l(struct Curl_easy *data,
(data->set.timecondition || data->set.get_filetime)) ? (data->set.timecondition || data->set.get_filetime)) ?
HD_VAL(hd, hdlen, "Last-Modified:") : NULL; HD_VAL(hd, hdlen, "Last-Modified:") : NULL;
if(v) { if(v) {
k->timeofdoc = Curl_getdate_capped(v); if(Curl_getdate_capped(v, &k->timeofdoc))
k->timeofdoc = 0;
if(data->set.get_filetime) if(data->set.get_filetime)
data->info.filetime = k->timeofdoc; data->info.filetime = k->timeofdoc;
return CURLE_OK; return CURLE_OK;
@ -3311,14 +3312,12 @@ static CURLcode http_header_r(struct Curl_easy *data,
if(v) { if(v) {
/* Retry-After = HTTP-date / delay-seconds */ /* Retry-After = HTTP-date / delay-seconds */
curl_off_t retry_after = 0; /* zero for unknown or "now" */ curl_off_t retry_after = 0; /* zero for unknown or "now" */
time_t date; time_t date = 0;
curlx_str_passblanks(&v); curlx_str_passblanks(&v);
/* try it as a date first, because a date can otherwise start with and /* try it as a date first, because a date can otherwise start with and
get treated as a number */ get treated as a number */
date = Curl_getdate_capped(v); if(!Curl_getdate_capped(v, &date)) {
if((time_t)-1 != date) {
time_t current = time(NULL); time_t current = time(NULL);
if(date >= current) if(date >= current)
/* convert date to number of seconds into the future */ /* convert date to number of seconds into the future */

View file

@ -589,26 +589,12 @@ time_t curl_getdate(const char *p, const time_t *now)
/* Curl_getdate_capped() differs from curl_getdate() in that this will return /* Curl_getdate_capped() differs from curl_getdate() in that this will return
TIME_T_MAX in case the parsed time value was too big, instead of an TIME_T_MAX in case the parsed time value was too big, instead of an
error. */ error. Returns non-zero on error. */
time_t Curl_getdate_capped(const char *p) int Curl_getdate_capped(const char *p, time_t *tp)
{ {
time_t parsed = -1; int rc = parsedate(p, tp);
int rc = parsedate(p, &parsed); return (rc == PARSEDATE_FAIL);
switch(rc) {
case PARSEDATE_OK:
if(parsed == (time_t)-1)
/* avoid returning -1 for a working scenario */
parsed++;
return parsed;
case PARSEDATE_LATER:
/* this returns the maximum time value */
return parsed;
default:
return -1; /* everything else is fail */
}
/* UNREACHABLE */
} }
/* /*

View file

@ -29,10 +29,10 @@ extern const char * const Curl_month[12];
CURLcode Curl_gmtime(time_t intime, struct tm *store); CURLcode Curl_gmtime(time_t intime, struct tm *store);
/* Curl_getdate_capped() differs from curl_getdate() in that this will return /* Curl_getdate_capped() differs from curl_getdate() in that this returns
TIME_T_MAX in case the parsed time value was too big, instead of an TIME_T_MAX in case the parsed time value was too big, instead of an
error. */ error. */
time_t Curl_getdate_capped(const char *p); int Curl_getdate_capped(const char *p, time_t *store);
#endif /* HEADER_CURL_PARSEDATE_H */ #endif /* HEADER_CURL_PARSEDATE_H */

View file

@ -1855,9 +1855,9 @@ static int myssh_in_SFTP_QUOTE_STAT(struct Curl_easy *data,
} }
else if(!strncmp(cmd, "atime", 5) || else if(!strncmp(cmd, "atime", 5) ||
!strncmp(cmd, "mtime", 5)) { !strncmp(cmd, "mtime", 5)) {
time_t date = Curl_getdate_capped(sshc->quote_path1); time_t date;
bool fail = FALSE; bool fail = FALSE;
if(date == -1) { if(Curl_getdate_capped(sshc->quote_path1, &date)) {
failf(data, "incorrect date format for %.*s", 5, cmd); failf(data, "incorrect date format for %.*s", 5, cmd);
fail = TRUE; fail = TRUE;
} }

View file

@ -1390,16 +1390,16 @@ sftp_quote_stat(struct Curl_easy *data,
} }
else if(!strncmp(cmd, "atime", 5) || else if(!strncmp(cmd, "atime", 5) ||
!strncmp(cmd, "mtime", 5)) { !strncmp(cmd, "mtime", 5)) {
time_t date = Curl_getdate_capped(sshc->quote_path1); time_t date;
bool fail = FALSE; bool fail = FALSE;
if(date == -1) { if(Curl_getdate_capped(sshc->quote_path1, &date)) {
failf(data, "incorrect date format for %.*s", 5, cmd); failf(data, "incorrect date format for %.*s", 5, cmd);
fail = TRUE; fail = TRUE;
} }
#if SIZEOF_TIME_T > SIZEOF_LONG #if SIZEOF_TIME_T > SIZEOF_LONG
if(date > 0xffffffff) { if(date > 0xffffffff) {
/* if 'long' cannot old >32-bit, this date cannot be sent */ /* if 'long' cannot hold >32-bit, this date cannot be sent */
failf(data, "date overflow"); failf(data, "date overflow");
fail = TRUE; fail = TRUE;
} }