imap: parse and use UIDVALIDITY as a number

Instead of a string. Saves a malloc, adds earlier format check.

RFC 3501 section 2.3.1.1 documents the value as a 32-bit value.

Closes #19188
This commit is contained in:
Daniel Stenberg 2025-10-22 11:25:42 +02:00
parent bb78c45407
commit fb6a4802d7
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2

View file

@ -115,8 +115,8 @@ struct imap_conn {
struct SASL sasl; /* SASL-related parameters */ struct SASL sasl; /* SASL-related parameters */
struct dynbuf dyn; /* for the IMAP commands */ struct dynbuf dyn; /* for the IMAP commands */
char *mailbox; /* The last selected mailbox */ char *mailbox; /* The last selected mailbox */
char *mailbox_uidvalidity; /* UIDVALIDITY parsed from select response */
imapstate state; /* Always use imap.c:state() to change state! */ imapstate state; /* Always use imap.c:state() to change state! */
unsigned int mb_uidvalidity; /* UIDVALIDITY parsed from select response */
char resptag[5]; /* Response tag to wait for */ char resptag[5]; /* Response tag to wait for */
unsigned char preftype; /* Preferred authentication type */ unsigned char preftype; /* Preferred authentication type */
unsigned char cmdid; /* Last used command ID */ unsigned char cmdid; /* Last used command ID */
@ -125,6 +125,7 @@ struct imap_conn {
BIT(tls_supported); /* StartTLS capability supported by server */ BIT(tls_supported); /* StartTLS capability supported by server */
BIT(login_disabled); /* LOGIN command disabled by server */ BIT(login_disabled); /* LOGIN command disabled by server */
BIT(ir_supported); /* Initial response supported by server */ BIT(ir_supported); /* Initial response supported by server */
BIT(mb_uidvalidity_set);
}; };
/* This IMAP struct is used in the Curl_easy. All IMAP data that is /* This IMAP struct is used in the Curl_easy. All IMAP data that is
@ -134,7 +135,6 @@ struct imap_conn {
struct IMAP { struct IMAP {
curl_pp_transfer transfer; curl_pp_transfer transfer;
char *mailbox; /* Mailbox to select */ char *mailbox; /* Mailbox to select */
char *uidvalidity; /* UIDVALIDITY to check in select */
char *uid; /* Message UID to fetch */ char *uid; /* Message UID to fetch */
char *mindex; /* Index in mail box of mail to fetch */ char *mindex; /* Index in mail box of mail to fetch */
char *section; /* Message SECTION to fetch */ char *section; /* Message SECTION to fetch */
@ -142,6 +142,8 @@ struct IMAP {
char *query; /* Query to search for */ char *query; /* Query to search for */
char *custom; /* Custom request */ char *custom; /* Custom request */
char *custom_params; /* Parameters for the custom request */ char *custom_params; /* Parameters for the custom request */
unsigned int uidvalidity; /* UIDVALIDITY to check in select */
BIT(uidvalidity_set);
}; };
@ -780,7 +782,6 @@ static CURLcode imap_perform_select(struct Curl_easy *data,
/* Invalidate old information as we are switching mailboxes */ /* Invalidate old information as we are switching mailboxes */
Curl_safefree(imapc->mailbox); Curl_safefree(imapc->mailbox);
Curl_safefree(imapc->mailbox_uidvalidity);
/* Check we have a mailbox */ /* Check we have a mailbox */
if(!imap->mailbox) { if(!imap->mailbox) {
@ -1222,24 +1223,19 @@ static CURLcode imap_state_select_resp(struct Curl_easy *data,
if(imapcode == '*') { if(imapcode == '*') {
/* See if this is an UIDVALIDITY response */ /* See if this is an UIDVALIDITY response */
if(checkprefix("OK [UIDVALIDITY ", line + 2)) { if(checkprefix("OK [UIDVALIDITY ", line + 2)) {
size_t len = 0; curl_off_t value;
const char *p = &line[2] + strlen("OK [UIDVALIDITY "); const char *p = &line[2] + strlen("OK [UIDVALIDITY ");
while((len < 20) && p[len] && ISDIGIT(p[len])) if(!curlx_str_number(&p, &value, UINT_MAX)) {
len++; imapc->mb_uidvalidity = (unsigned int)value;
if(len && (p[len] == ']')) { imapc->mb_uidvalidity_set = TRUE;
struct dynbuf uid;
curlx_dyn_init(&uid, 20);
if(curlx_dyn_addn(&uid, p, len))
return CURLE_OUT_OF_MEMORY;
free(imapc->mailbox_uidvalidity);
imapc->mailbox_uidvalidity = curlx_dyn_ptr(&uid);
} }
} }
} }
else if(imapcode == IMAP_RESP_OK) { else if(imapcode == IMAP_RESP_OK) {
/* Check if the UIDVALIDITY has been specified and matches */ /* Check if the UIDVALIDITY has been specified and matches */
if(imap->uidvalidity && imapc->mailbox_uidvalidity && if(imap->uidvalidity_set && imapc->mb_uidvalidity_set &&
!curl_strequal(imap->uidvalidity, imapc->mailbox_uidvalidity)) { (imap->uidvalidity != imapc->mb_uidvalidity)) {
failf(data, "Mailbox UIDVALIDITY has changed"); failf(data, "Mailbox UIDVALIDITY has changed");
result = CURLE_REMOTE_FILE_NOT_FOUND; result = CURLE_REMOTE_FILE_NOT_FOUND;
} }
@ -1690,8 +1686,8 @@ static CURLcode imap_perform(struct Curl_easy *data, bool *connected,
has already been selected on this connection */ has already been selected on this connection */
if(imap->mailbox && imapc->mailbox && if(imap->mailbox && imapc->mailbox &&
curl_strequal(imap->mailbox, imapc->mailbox) && curl_strequal(imap->mailbox, imapc->mailbox) &&
(!imap->uidvalidity || !imapc->mailbox_uidvalidity || (!imap->uidvalidity_set || !imapc->mb_uidvalidity_set ||
curl_strequal(imap->uidvalidity, imapc->mailbox_uidvalidity))) (imap->uidvalidity == imapc->mb_uidvalidity)))
selected = TRUE; selected = TRUE;
/* Start the first command in the DO phase */ /* Start the first command in the DO phase */
@ -1861,7 +1857,6 @@ static CURLcode imap_regular_transfer(struct Curl_easy *data,
static void imap_easy_reset(struct IMAP *imap) static void imap_easy_reset(struct IMAP *imap)
{ {
Curl_safefree(imap->mailbox); Curl_safefree(imap->mailbox);
Curl_safefree(imap->uidvalidity);
Curl_safefree(imap->uid); Curl_safefree(imap->uid);
Curl_safefree(imap->mindex); Curl_safefree(imap->mindex);
Curl_safefree(imap->section); Curl_safefree(imap->section);
@ -1890,7 +1885,6 @@ static void imap_conn_dtor(void *key, size_t klen, void *entry)
Curl_pp_disconnect(&imapc->pp); Curl_pp_disconnect(&imapc->pp);
curlx_dyn_free(&imapc->dyn); curlx_dyn_free(&imapc->dyn);
Curl_safefree(imapc->mailbox); Curl_safefree(imapc->mailbox);
Curl_safefree(imapc->mailbox_uidvalidity);
free(imapc); free(imapc);
} }
@ -2177,54 +2171,45 @@ static CURLcode imap_parse_url_path(struct Curl_easy *data,
DEBUGF(infof(data, "IMAP URL parameter '%s' = '%s'", name, value)); DEBUGF(infof(data, "IMAP URL parameter '%s' = '%s'", name, value));
/* Process the known hierarchical parameters (UIDVALIDITY, UID, SECTION and /* Process the known hierarchical parameters (UIDVALIDITY, UID, SECTION
PARTIAL) stripping of the trailing slash character if it is present. and PARTIAL) stripping of the trailing slash character if it is
present.
Note: Unknown parameters trigger a URL_MALFORMAT error. */ Note: Unknown parameters trigger a URL_MALFORMAT error. */
if(curl_strequal(name, "UIDVALIDITY") && !imap->uidvalidity) { if(valuelen > 0 && value[valuelen - 1] == '/')
if(valuelen > 0 && value[valuelen - 1] == '/') value[valuelen - 1] = '\0';
value[valuelen - 1] = '\0'; if(valuelen) {
if(curl_strequal(name, "UIDVALIDITY") && !imap->uidvalidity_set) {
imap->uidvalidity = value; curl_off_t num;
value = NULL; const char *p = (const char *)value;
if(!curlx_str_number(&p, &num, UINT_MAX)) {
imap->uidvalidity = (unsigned int)num;
imap->uidvalidity_set = TRUE;
}
free(value);
}
else if(curl_strequal(name, "UID") && !imap->uid) {
imap->uid = value;
}
else if(curl_strequal(name, "MAILINDEX") && !imap->mindex) {
imap->mindex = value;
}
else if(curl_strequal(name, "SECTION") && !imap->section) {
imap->section = value;
}
else if(curl_strequal(name, "PARTIAL") && !imap->partial) {
imap->partial = value;
}
else {
free(name);
free(value);
return CURLE_URL_MALFORMAT;
}
} }
else if(curl_strequal(name, "UID") && !imap->uid) { else
if(valuelen > 0 && value[valuelen - 1] == '/') /* blank? */
value[valuelen - 1] = '\0';
imap->uid = value;
value = NULL;
}
else if(curl_strequal(name, "MAILINDEX") && !imap->mindex) {
if(valuelen > 0 && value[valuelen - 1] == '/')
value[valuelen - 1] = '\0';
imap->mindex = value;
value = NULL;
}
else if(curl_strequal(name, "SECTION") && !imap->section) {
if(valuelen > 0 && value[valuelen - 1] == '/')
value[valuelen - 1] = '\0';
imap->section = value;
value = NULL;
}
else if(curl_strequal(name, "PARTIAL") && !imap->partial) {
if(valuelen > 0 && value[valuelen - 1] == '/')
value[valuelen - 1] = '\0';
imap->partial = value;
value = NULL;
}
else {
free(name);
free(value); free(value);
return CURLE_URL_MALFORMAT;
}
free(name); free(name);
free(value);
} }
/* Does the URL contain a query parameter? Only valid when we have a mailbox /* Does the URL contain a query parameter? Only valid when we have a mailbox