diff options
Diffstat (limited to 'plugins/FTPFileYM/curl/lib/imap.c')
-rw-r--r-- | plugins/FTPFileYM/curl/lib/imap.c | 1840 |
1 files changed, 1305 insertions, 535 deletions
diff --git a/plugins/FTPFileYM/curl/lib/imap.c b/plugins/FTPFileYM/curl/lib/imap.c index 72ec871f92..9a845102ad 100644 --- a/plugins/FTPFileYM/curl/lib/imap.c +++ b/plugins/FTPFileYM/curl/lib/imap.c @@ -24,7 +24,9 @@ * RFC3501 IMAPv4 protocol * RFC4422 Simple Authentication and Security Layer (SASL) * RFC4616 PLAIN authentication + * RFC4959 IMAP Extension for SASL Initial Client Response * RFC5092 IMAP URL Scheme + * RFC6749 OAuth 2.0 Authorization Framework * ***************************************************************************/ @@ -76,6 +78,7 @@ #include "url.h" #include "rawstr.h" #include "curl_sasl.h" +#include "warnless.h" #define _MPRINTF_REPLACE /* use our functions only */ #include <curl/mprintf.h> @@ -85,7 +88,6 @@ #include "memdebug.h" /* Local API functions */ -static CURLcode imap_parse_url_path(struct connectdata *conn); static CURLcode imap_regular_transfer(struct connectdata *conn, bool *done); static CURLcode imap_do(struct connectdata *conn, bool *done); static CURLcode imap_done(struct connectdata *conn, CURLcode status, @@ -97,7 +99,11 @@ static int imap_getsock(struct connectdata *conn, curl_socket_t *socks, int numsocks); static CURLcode imap_doing(struct connectdata *conn, bool *dophase_done); static CURLcode imap_setup_connection(struct connectdata *conn); -static CURLcode imap_state_upgrade_tls(struct connectdata *conn); +static char *imap_atom(const char *str); +static CURLcode imap_sendf(struct connectdata *conn, const char *fmt, ...); +static CURLcode imap_parse_url_options(struct connectdata *conn); +static CURLcode imap_parse_url_path(struct connectdata *conn); +static CURLcode imap_parse_custom_request(struct connectdata *conn); /* * IMAP protocol handler. @@ -158,7 +164,7 @@ const struct Curl_handler Curl_handler_imaps = { static const struct Curl_handler Curl_handler_imap_proxy = { "IMAP", /* scheme */ - ZERO_NULL, /* setup_connection */ + Curl_http_setup_conn, /* setup_connection */ Curl_http, /* do_it */ Curl_http_done, /* done */ ZERO_NULL, /* do_more */ @@ -183,7 +189,7 @@ static const struct Curl_handler Curl_handler_imap_proxy = { static const struct Curl_handler Curl_handler_imaps_proxy = { "IMAPS", /* scheme */ - ZERO_NULL, /* setup_connection */ + Curl_http_setup_conn, /* setup_connection */ Curl_http, /* do_it */ Curl_http_done, /* done */ ZERO_NULL, /* do_more */ @@ -203,227 +209,173 @@ static const struct Curl_handler Curl_handler_imaps_proxy = { #endif #endif +#ifdef USE_SSL +static void imap_to_imaps(struct connectdata *conn) +{ + conn->handler = &Curl_handler_imaps; +} +#else +#define imap_to_imaps(x) Curl_nop_stmt +#endif + /*********************************************************************** * - * imap_sendf() + * imap_matchresp() * - * Sends the formated string as an IMAP command to the server. + * Determines whether the untagged response is related to the specified + * command by checking if it is in format "* <command-name> ..." or + * "* <number> <command-name> ...". * - * Designed to never block. + * The "* " marker is assumed to have already been checked by the caller. */ -static CURLcode imap_sendf(struct connectdata *conn, - const char *idstr, /* command id to wait for */ - const char *fmt, ...) +static bool imap_matchresp(const char *line, size_t len, const char *cmd) { - CURLcode res; - struct imap_conn *imapc = &conn->proto.imapc; - va_list ap; - va_start(ap, fmt); - - imapc->idstr = idstr; + const char *end = line + len; + size_t cmd_len = strlen(cmd); - res = Curl_pp_vsendf(&imapc->pp, fmt, ap); + /* Skip the untagged response marker */ + line += 2; - va_end(ap); - - return res; -} + /* Do we have a number after the marker? */ + if(line < end && ISDIGIT(*line)) { + /* Skip the number */ + do + line++; + while(line < end && ISDIGIT(*line)); -static const char *getcmdid(struct connectdata *conn) -{ - static const char * const ids[]= { - "A", - "B", - "C", - "D" - }; + /* Do we have the space character? */ + if(line == end || *line != ' ') + return FALSE; - struct imap_conn *imapc = &conn->proto.imapc; + line++; + } - /* Get the next id, but wrap at end of table */ - imapc->cmdid = (int)((imapc->cmdid + 1) % (sizeof(ids) / sizeof(ids[0]))); + /* Does the command name match and is it followed by a space character or at + the end of line? */ + if(line + cmd_len <= end && Curl_raw_nequal(line, cmd, cmd_len) && + (line[cmd_len] == ' ' || line + cmd_len == end)) + return TRUE; - return ids[imapc->cmdid]; + return FALSE; } /*********************************************************************** * - * imap_atom() - * - * Checks the input string for characters that need escaping and returns an - * atom ready for sending to the server. - * - * The returned string needs to be freed. + * imap_endofresp() * + * Checks whether the given string is a valid tagged, untagged or continuation + * response which can be processed by the response handler. */ -static char* imap_atom(const char* str) +static bool imap_endofresp(struct connectdata *conn, char *line, size_t len, + int *resp) { - const char *p1; - char *p2; - size_t backsp_count = 0; - size_t quote_count = 0; - bool space_exists = FALSE; - size_t newlen = 0; - char *newstr = NULL; - - if(!str) - return NULL; + struct IMAP *imap = conn->data->req.protop; + struct imap_conn *imapc = &conn->proto.imapc; + const char *id = imapc->resptag; + size_t id_len = strlen(id); - /* Count any unescapped characters */ - p1 = str; - while(*p1) { - if(*p1 == '\\') - backsp_count++; - else if(*p1 == '"') - quote_count++; - else if(*p1 == ' ') - space_exists = TRUE; + /* Do we have a tagged command response? */ + if(len >= id_len + 1 && !memcmp(id, line, id_len) && line[id_len] == ' ') { + line += id_len + 1; + len -= id_len + 1; + + if(len >= 2 && !memcmp(line, "OK", 2)) + *resp = 'O'; + else if(len >= 2 && !memcmp(line, "NO", 2)) + *resp = 'N'; + else if(len >= 3 && !memcmp(line, "BAD", 3)) + *resp = 'B'; + else { + failf(conn->data, "Bad tagged response"); + *resp = -1; + } - p1++; + return TRUE; } - /* Does the input contain any unescapped characters? */ - if(!backsp_count && !quote_count && !space_exists) - return strdup(str); + /* Do we have an untagged command response? */ + if(len >= 2 && !memcmp("* ", line, 2)) { + switch(imapc->state) { + /* States which are interested in untagged responses */ + case IMAP_CAPABILITY: + if(!imap_matchresp(line, len, "CAPABILITY")) + return FALSE; + break; - /* Calculate the new string length */ - newlen = strlen(str) + backsp_count + quote_count + (space_exists ? 2 : 0); + case IMAP_LIST: + if((!imap->custom && !imap_matchresp(line, len, "LIST")) || + (imap->custom && !imap_matchresp(line, len, imap->custom) && + (strcmp(imap->custom, "STORE") || + !imap_matchresp(line, len, "FETCH")) && + strcmp(imap->custom, "SELECT") && + strcmp(imap->custom, "EXAMINE") && + strcmp(imap->custom, "SEARCH") && + strcmp(imap->custom, "EXPUNGE") && + strcmp(imap->custom, "LSUB") && + strcmp(imap->custom, "UID") && + strcmp(imap->custom, "NOOP"))) + return FALSE; + break; - /* Allocate the new string */ - newstr = (char *) malloc((newlen + 1) * sizeof(char)); - if(!newstr) - return NULL; + case IMAP_SELECT: + /* SELECT is special in that its untagged responses do not have a + common prefix so accept anything! */ + break; - /* Surround the string in quotes if necessary */ - p2 = newstr; - if(space_exists) { - newstr[0] = '"'; - newstr[newlen - 1] = '"'; - p2++; - } + case IMAP_FETCH: + if(!imap_matchresp(line, len, "FETCH")) + return FALSE; + break; - /* Copy the string, escaping backslash and quote characters along the way */ - p1 = str; - while(*p1) { - if(*p1 == '\\' || *p1 == '"') { - *p2 = '\\'; - p2++; + /* Ignore other untagged responses */ + default: + return FALSE; } - *p2 = *p1; - - p1++; - p2++; - } - - /* Terminate the string */ - newstr[newlen] = '\0'; - - return newstr; -} - -/* Function that checks for an ending imap status code at the start of the - given string but also detects the supported authentication mechanisms from - the CAPABILITY response. */ -static int imap_endofresp(struct pingpong *pp, int *resp) -{ - char *line = pp->linestart_resp; - size_t len = pp->nread_resp; - struct imap_conn *imapc = &pp->conn->proto.imapc; - const char *id = imapc->idstr; - size_t id_len = strlen(id); - size_t wordlen; - - /* Do we have a generic command response? */ - if(len >= id_len + 3) { - if(!memcmp(id, line, id_len) && line[id_len] == ' ') { - *resp = line[id_len + 1]; /* O, N or B */ - return TRUE; - } + *resp = '*'; + return TRUE; } - /* Do we have a generic continuation response? */ + /* Do we have a continuation response? This should be a + symbol followed by + a space and optionally some text as per RFC-3501 for the AUTHENTICATE and + APPEND commands and as outlined in Section 4. Examples of RFC-4959 but + some e-mail servers ignore this and only send a single + instead. */ if((len == 3 && !memcmp("+", line, 1)) || (len >= 2 && !memcmp("+ ", line, 2))) { - *resp = '+'; - return TRUE; - } + switch(imapc->state) { + /* States which are interested in continuation responses */ + case IMAP_AUTHENTICATE_PLAIN: + case IMAP_AUTHENTICATE_LOGIN: + case IMAP_AUTHENTICATE_LOGIN_PASSWD: + case IMAP_AUTHENTICATE_CRAMMD5: + case IMAP_AUTHENTICATE_DIGESTMD5: + case IMAP_AUTHENTICATE_DIGESTMD5_RESP: + case IMAP_AUTHENTICATE_NTLM: + case IMAP_AUTHENTICATE_NTLM_TYPE2MSG: + case IMAP_AUTHENTICATE_XOAUTH2: + case IMAP_AUTHENTICATE_FINAL: + case IMAP_APPEND: + *resp = '+'; + break; - /* Are we processing CAPABILITY command responses? */ - if(imapc->state == IMAP_CAPABILITY) { - /* Do we have a valid response? */ - if(len >= 2 && !memcmp("* ", line, 2)) { - line += 2; - len -= 2; - - /* Loop through the data line */ - for(;;) { - while(len && - (*line == ' ' || *line == '\t' || - *line == '\r' || *line == '\n')) { - - if(*line == '\n') - return FALSE; - - line++; - len--; - } - - if(!len) - break; - - /* Extract the word */ - for(wordlen = 0; wordlen < len && line[wordlen] != ' ' && - line[wordlen] != '\t' && line[wordlen] != '\r' && - line[wordlen] != '\n';) - wordlen++; - - /* Has the server explicitly disabled clear text authentication? */ - if(wordlen == 13 && !memcmp(line, "LOGINDISABLED", 13)) - imapc->login_disabled = TRUE; - - /* Do we have a SASL based authentication mechanism? */ - else if(wordlen > 5 && !memcmp(line, "AUTH=", 5)) { - line += 5; - len -= 5; - wordlen -= 5; - - /* Test the word for a matching authentication mechanism */ - if(wordlen == 5 && !memcmp(line, "LOGIN", 5)) - imapc->authmechs |= SASL_MECH_LOGIN; - if(wordlen == 5 && !memcmp(line, "PLAIN", 5)) - imapc->authmechs |= SASL_MECH_PLAIN; - else if(wordlen == 8 && !memcmp(line, "CRAM-MD5", 8)) - imapc->authmechs |= SASL_MECH_CRAM_MD5; - else if(wordlen == 10 && !memcmp(line, "DIGEST-MD5", 10)) - imapc->authmechs |= SASL_MECH_DIGEST_MD5; - else if(wordlen == 6 && !memcmp(line, "GSSAPI", 6)) - imapc->authmechs |= SASL_MECH_GSSAPI; - else if(wordlen == 8 && !memcmp(line, "EXTERNAL", 8)) - imapc->authmechs |= SASL_MECH_EXTERNAL; - else if(wordlen == 4 && !memcmp(line, "NTLM", 4)) - imapc->authmechs |= SASL_MECH_NTLM; - } - - line += wordlen; - len -= wordlen; - } + default: + failf(conn->data, "Unexpected continuation response"); + *resp = -1; + break; } - } - /* Are we processing FETCH command responses? */ - if(imapc->state == IMAP_FETCH) { - /* Do we have a valid response? */ - if(len >= 2 && !memcmp("* ", line, 2)) { - *resp = '*'; - return TRUE; - } + return TRUE; } return FALSE; /* Nothing for us */ } -/* This is the ONLY way to change IMAP state! */ +/*********************************************************************** + * + * state() + * + * This is the ONLY way to change IMAP state! + */ static void state(struct connectdata *conn, imapstate newstate) { struct imap_conn *imapc = &conn->proto.imapc; @@ -432,9 +384,9 @@ static void state(struct connectdata *conn, imapstate newstate) static const char * const names[]={ "STOP", "SERVERGREET", + "CAPABILITY", "STARTTLS", "UPGRADETLS", - "CAPABILITY", "AUTHENTICATE_PLAIN", "AUTHENTICATE_LOGIN", "AUTHENTICATE_LOGIN_PASSWD", @@ -443,152 +395,425 @@ static void state(struct connectdata *conn, imapstate newstate) "AUTHENTICATE_DIGESTMD5_RESP", "AUTHENTICATE_NTLM", "AUTHENTICATE_NTLM_TYPE2MSG", - "AUTHENTICATE", + "AUTHENTICATE_XOAUTH2", + "AUTHENTICATE_FINAL", "LOGIN", + "LIST", "SELECT", "FETCH", + "FETCH_FINAL", + "APPEND", + "APPEND_FINAL", "LOGOUT", /* LAST */ }; if(imapc->state != newstate) infof(conn->data, "IMAP %p state change from %s to %s\n", - imapc, names[imapc->state], names[newstate]); + (void *)imapc, names[imapc->state], names[newstate]); #endif imapc->state = newstate; } -static CURLcode imap_state_capability(struct connectdata *conn) +/*********************************************************************** + * + * imap_perform_capability() + * + * Sends the CAPABILITY command in order to obtain a list of server side + * supported capabilities. + */ +static CURLcode imap_perform_capability(struct connectdata *conn) { CURLcode result = CURLE_OK; struct imap_conn *imapc = &conn->proto.imapc; - const char *str; imapc->authmechs = 0; /* No known authentication mechanisms yet */ imapc->authused = 0; /* Clear the authentication mechanism used */ + imapc->tls_supported = FALSE; /* Clear the TLS capability */ - /* Check we have a username and password to authenticate with and end the - connect phase if we don't */ - if(!conn->bits.user_passwd) { - state(conn, IMAP_STOP); + /* Send the CAPABILITY command */ + result = imap_sendf(conn, "CAPABILITY"); - return result; - } + if(!result) + state(conn, IMAP_CAPABILITY); - str = getcmdid(conn); + return result; +} - /* Send the CAPABILITY command */ - result = imap_sendf(conn, str, "%s CAPABILITY", str); +/*********************************************************************** + * + * imap_perform_starttls() + * + * Sends the STARTTLS command to start the upgrade to TLS. + */ +static CURLcode imap_perform_starttls(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; - if(result) - return result; + /* Send the STARTTLS command */ + result = imap_sendf(conn, "STARTTLS"); - state(conn, IMAP_CAPABILITY); + if(!result) + state(conn, IMAP_STARTTLS); - return CURLE_OK; + return result; +} + +/*********************************************************************** + * + * imap_perform_upgrade_tls() + * + * Performs the upgrade to TLS. + */ +static CURLcode imap_perform_upgrade_tls(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct imap_conn *imapc = &conn->proto.imapc; + + /* Start the SSL connection */ + result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, &imapc->ssldone); + + if(!result) { + if(imapc->state != IMAP_UPGRADETLS) + state(conn, IMAP_UPGRADETLS); + + if(imapc->ssldone) { + imap_to_imaps(conn); + result = imap_perform_capability(conn); + } + } + + return result; } -static CURLcode imap_state_login(struct connectdata *conn) +/*********************************************************************** + * + * imap_perform_login() + * + * Sends a clear text LOGIN command to authenticate with. + */ +static CURLcode imap_perform_login(struct connectdata *conn) { - CURLcode result; - struct FTP *imap = conn->data->state.proto.imap; - const char *str = getcmdid(conn); - char *user = imap_atom(imap->user); - char *passwd = imap_atom(imap->passwd); + CURLcode result = CURLE_OK; + char *user; + char *passwd; - /* send USER and password */ - result = imap_sendf(conn, str, "%s LOGIN %s %s", str, - user ? user : "", passwd ? passwd : ""); + /* Check we have a username and password to authenticate with and end the + connect phase if we don't */ + if(!conn->bits.user_passwd) { + state(conn, IMAP_STOP); + + return result; + } + + /* Make sure the username and password are in the correct atom format */ + user = imap_atom(conn->user); + passwd = imap_atom(conn->passwd); + + /* Send the LOGIN command */ + result = imap_sendf(conn, "LOGIN %s %s", user ? user : "", + passwd ? passwd : ""); Curl_safefree(user); Curl_safefree(passwd); - if(result) - return result; - - state(conn, IMAP_LOGIN); + if(!result) + state(conn, IMAP_LOGIN); - return CURLE_OK; + return result; } -static CURLcode imap_authenticate(struct connectdata *conn) +/*********************************************************************** + * + * imap_perform_authenticate() + * + * Sends an AUTHENTICATE command allowing the client to login with the + * appropriate SASL authentication mechanism. + * + * Additionally, the function will perform fallback to the LOGIN command + * should a common mechanism not be available between the client and server. + */ +static CURLcode imap_perform_authenticate(struct connectdata *conn) { CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; struct imap_conn *imapc = &conn->proto.imapc; const char *mech = NULL; - imapstate authstate = IMAP_STOP; + char *initresp = NULL; + size_t len = 0; + imapstate state1 = IMAP_STOP; + imapstate state2 = IMAP_STOP; + + /* Check we have a username and password to authenticate with and end the + connect phase if we don't */ + if(!conn->bits.user_passwd) { + state(conn, IMAP_STOP); + + return result; + } /* Calculate the supported authentication mechanism by decreasing order of security */ #ifndef CURL_DISABLE_CRYPTO_AUTH - if(imapc->authmechs & SASL_MECH_DIGEST_MD5) { - mech = "DIGEST-MD5"; - authstate = IMAP_AUTHENTICATE_DIGESTMD5; + if((imapc->authmechs & SASL_MECH_DIGEST_MD5) && + (imapc->prefmech & SASL_MECH_DIGEST_MD5)) { + mech = SASL_MECH_STRING_DIGEST_MD5; + state1 = IMAP_AUTHENTICATE_DIGESTMD5; imapc->authused = SASL_MECH_DIGEST_MD5; } - else if(imapc->authmechs & SASL_MECH_CRAM_MD5) { - mech = "CRAM-MD5"; - authstate = IMAP_AUTHENTICATE_CRAMMD5; + else if((imapc->authmechs & SASL_MECH_CRAM_MD5) && + (imapc->prefmech & SASL_MECH_CRAM_MD5)) { + mech = SASL_MECH_STRING_CRAM_MD5; + state1 = IMAP_AUTHENTICATE_CRAMMD5; imapc->authused = SASL_MECH_CRAM_MD5; } else #endif #ifdef USE_NTLM - if(imapc->authmechs & SASL_MECH_NTLM) { - mech = "NTLM"; - authstate = IMAP_AUTHENTICATE_NTLM; + if((imapc->authmechs & SASL_MECH_NTLM) && + (imapc->prefmech & SASL_MECH_NTLM)) { + mech = SASL_MECH_STRING_NTLM; + state1 = IMAP_AUTHENTICATE_NTLM; + state2 = IMAP_AUTHENTICATE_NTLM_TYPE2MSG; imapc->authused = SASL_MECH_NTLM; + + if(imapc->ir_supported || data->set.sasl_ir) + result = Curl_sasl_create_ntlm_type1_message(conn->user, conn->passwd, + &conn->ntlm, + &initresp, &len); } else #endif - if(imapc->authmechs & SASL_MECH_LOGIN) { - mech = "LOGIN"; - authstate = IMAP_AUTHENTICATE_LOGIN; + if(((imapc->authmechs & SASL_MECH_XOAUTH2) && + (imapc->prefmech & SASL_MECH_XOAUTH2) && + (imapc->prefmech != SASL_AUTH_ANY)) || conn->xoauth2_bearer) { + mech = SASL_MECH_STRING_XOAUTH2; + state1 = IMAP_AUTHENTICATE_XOAUTH2; + state2 = IMAP_AUTHENTICATE_FINAL; + imapc->authused = SASL_MECH_XOAUTH2; + + if(imapc->ir_supported || data->set.sasl_ir) + result = Curl_sasl_create_xoauth2_message(conn->data, conn->user, + conn->xoauth2_bearer, + &initresp, &len); + } + else if((imapc->authmechs & SASL_MECH_LOGIN) && + (imapc->prefmech & SASL_MECH_LOGIN)) { + mech = SASL_MECH_STRING_LOGIN; + state1 = IMAP_AUTHENTICATE_LOGIN; + state2 = IMAP_AUTHENTICATE_LOGIN_PASSWD; imapc->authused = SASL_MECH_LOGIN; + + if(imapc->ir_supported || data->set.sasl_ir) + result = Curl_sasl_create_login_message(conn->data, conn->user, + &initresp, &len); } - else if(imapc->authmechs & SASL_MECH_PLAIN) { - mech = "PLAIN"; - authstate = IMAP_AUTHENTICATE_PLAIN; + else if((imapc->authmechs & SASL_MECH_PLAIN) && + (imapc->prefmech & SASL_MECH_PLAIN)) { + mech = SASL_MECH_STRING_PLAIN; + state1 = IMAP_AUTHENTICATE_PLAIN; + state2 = IMAP_AUTHENTICATE_FINAL; imapc->authused = SASL_MECH_PLAIN; + + if(imapc->ir_supported || data->set.sasl_ir) + result = Curl_sasl_create_plain_message(conn->data, conn->user, + conn->passwd, &initresp, &len); } - if(mech) { - /* Perform SASL based authentication */ - const char *str = getcmdid(conn); + if(!result) { + if(mech) { + /* Perform SASL based authentication */ + if(initresp) { + result = imap_sendf(conn, "AUTHENTICATE %s %s", mech, initresp); + + if(!result) + state(conn, state2); + } + else { + result = imap_sendf(conn, "AUTHENTICATE %s", mech); - result = imap_sendf(conn, str, "%s AUTHENTICATE %s", str, mech); + if(!result) + state(conn, state1); + } - if(!result) - state(conn, authstate); + Curl_safefree(initresp); + } + else if(!imapc->login_disabled) + /* Perform clear text authentication */ + result = imap_perform_login(conn); + else { + /* Other mechanisms not supported */ + infof(conn->data, "No known authentication mechanisms supported!\n"); + result = CURLE_LOGIN_DENIED; + } } - else if(!imapc->login_disabled) - /* Perform clear text authentication */ - result = imap_state_login(conn); + + return result; +} + +/*********************************************************************** + * + * imap_perform_list() + * + * Sends a LIST command or an alternative custom request. + */ +static CURLcode imap_perform_list(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + struct IMAP *imap = data->req.protop; + char *mailbox; + + if(imap->custom) + /* Send the custom request */ + result = imap_sendf(conn, "%s%s", imap->custom, + imap->custom_params ? imap->custom_params : ""); else { - /* Other mechanisms not supported */ - infof(conn->data, "No known authentication mechanisms supported!\n"); - result = CURLE_LOGIN_DENIED; + /* Make sure the mailbox is in the correct atom format */ + mailbox = imap_atom(imap->mailbox ? imap->mailbox : ""); + if(!mailbox) + return CURLE_OUT_OF_MEMORY; + + /* Send the LIST command */ + result = imap_sendf(conn, "LIST \"%s\" *", mailbox); + + Curl_safefree(mailbox); } + if(!result) + state(conn, IMAP_LIST); + return result; } -/* For the IMAP "protocol connect" and "doing" phases only */ -static int imap_getsock(struct connectdata *conn, curl_socket_t *socks, - int numsocks) +/*********************************************************************** + * + * imap_perform_select() + * + * Sends a SELECT command to ask the server to change the selected mailbox. + */ +static CURLcode imap_perform_select(struct connectdata *conn) { - return Curl_pp_getsock(&conn->proto.imapc.pp, socks, numsocks); + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + struct IMAP *imap = data->req.protop; + struct imap_conn *imapc = &conn->proto.imapc; + char *mailbox; + + /* Invalidate old information as we are switching mailboxes */ + Curl_safefree(imapc->mailbox); + Curl_safefree(imapc->mailbox_uidvalidity); + + /* Check we have a mailbox */ + if(!imap->mailbox) { + failf(conn->data, "Cannot SELECT without a mailbox."); + return CURLE_URL_MALFORMAT; + } + + /* Make sure the mailbox is in the correct atom format */ + mailbox = imap_atom(imap->mailbox); + if(!mailbox) + return CURLE_OUT_OF_MEMORY; + + /* Send the SELECT command */ + result = imap_sendf(conn, "SELECT %s", mailbox); + + Curl_safefree(mailbox); + + if(!result) + state(conn, IMAP_SELECT); + + return result; } -#ifdef USE_SSL -static void imap_to_imaps(struct connectdata *conn) +/*********************************************************************** + * + * imap_perform_fetch() + * + * Sends a FETCH command to initiate the download of a message. + */ +static CURLcode imap_perform_fetch(struct connectdata *conn) { - conn->handler = &Curl_handler_imaps; + CURLcode result = CURLE_OK; + struct IMAP *imap = conn->data->req.protop; + + /* Check we have a UID */ + if(!imap->uid) { + failf(conn->data, "Cannot FETCH without a UID."); + return CURLE_URL_MALFORMAT; + } + + /* Send the FETCH command */ + result = imap_sendf(conn, "FETCH %s BODY[%s]", + imap->uid, + imap->section ? imap->section : ""); + + if(!result) + state(conn, IMAP_FETCH); + + return result; +} + +/*********************************************************************** + * + * imap_perform_append() + * + * Sends an APPEND command to initiate the upload of a message. + */ +static CURLcode imap_perform_append(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct IMAP *imap = conn->data->req.protop; + char *mailbox; + + /* Check we have a mailbox */ + if(!imap->mailbox) { + failf(conn->data, "Cannot APPEND without a mailbox."); + return CURLE_URL_MALFORMAT; + } + + /* Check we know the size of the upload */ + if(conn->data->set.infilesize < 0) { + failf(conn->data, "Cannot APPEND with unknown input file size\n"); + return CURLE_UPLOAD_FAILED; + } + + /* Make sure the mailbox is in the correct atom format */ + mailbox = imap_atom(imap->mailbox); + if(!mailbox) + return CURLE_OUT_OF_MEMORY; + + /* Send the APPEND command */ + result = imap_sendf(conn, "APPEND %s (\\Seen) {%" FORMAT_OFF_T "}", + mailbox, conn->data->set.infilesize); + + Curl_safefree(mailbox); + + if(!result) + state(conn, IMAP_APPEND); + + return result; +} + +/*********************************************************************** + * + * imap_perform_logout() + * + * Performs the logout action prior to sclose() being called. + */ +static CURLcode imap_perform_logout(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + + /* Send the LOGOUT command */ + result = imap_sendf(conn, "LOGOUT"); + + if(!result) + state(conn, IMAP_LOGOUT); + + return result; } -#else -#define imap_to_imaps(x) Curl_nop_stmt -#endif /* For the initial server greeting */ static CURLcode imap_state_servergreet_resp(struct connectdata *conn, @@ -602,85 +827,136 @@ static CURLcode imap_state_servergreet_resp(struct connectdata *conn, if(imapcode != 'O') { failf(data, "Got unexpected imap-server response"); - return CURLE_FTP_WEIRD_SERVER_REPLY; - } - - if(data->set.use_ssl && !conn->ssl[FIRSTSOCKET].use) { - /* We don't have a SSL/TLS connection yet, but SSL is requested. Switch - to TLS connection now */ - const char *str = getcmdid(conn); - result = imap_sendf(conn, str, "%s STARTTLS", str); - if(!result) - state(conn, IMAP_STARTTLS); + result = CURLE_FTP_WEIRD_SERVER_REPLY; /* TODO: fix this code */ } else - result = imap_state_capability(conn); + result = imap_perform_capability(conn); return result; } -/* For STARTTLS responses */ -static CURLcode imap_state_starttls_resp(struct connectdata *conn, - int imapcode, - imapstate instate) +/* For CAPABILITY responses */ +static CURLcode imap_state_capability_resp(struct connectdata *conn, + int imapcode, + imapstate instate) { CURLcode result = CURLE_OK; struct SessionHandle *data = conn->data; + struct imap_conn *imapc = &conn->proto.imapc; + const char *line = data->state.buffer; + size_t wordlen; (void)instate; /* no use for this yet */ - if(imapcode != 'O') { - if(data->set.use_ssl != CURLUSESSL_TRY) { - failf(data, "STARTTLS denied. %c", imapcode); - result = CURLE_USE_SSL_FAILED; - } - else - result = imap_state_capability(conn); - } - else - result = imap_state_upgrade_tls(conn); + /* Do we have a untagged response? */ + if(imapcode == '*') { + line += 2; - return result; -} + /* Loop through the data line */ + for(;;) { + while(*line && + (*line == ' ' || *line == '\t' || + *line == '\r' || *line == '\n')) { -static CURLcode imap_state_upgrade_tls(struct connectdata *conn) -{ - struct imap_conn *imapc = &conn->proto.imapc; - CURLcode result; + line++; + } - result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, &imapc->ssldone); + if(!*line) + break; - if(!result) { - if(imapc->state != IMAP_UPGRADETLS) - state(conn, IMAP_UPGRADETLS); + /* Extract the word */ + for(wordlen = 0; line[wordlen] && line[wordlen] != ' ' && + line[wordlen] != '\t' && line[wordlen] != '\r' && + line[wordlen] != '\n';) + wordlen++; + + /* Does the server support the STARTTLS capability? */ + if(wordlen == 8 && !memcmp(line, "STARTTLS", 8)) + imapc->tls_supported = TRUE; + + /* Has the server explicitly disabled clear text authentication? */ + else if(wordlen == 13 && !memcmp(line, "LOGINDISABLED", 13)) + imapc->login_disabled = TRUE; + + /* Does the server support the SASL-IR capability? */ + else if(wordlen == 7 && !memcmp(line, "SASL-IR", 7)) + imapc->ir_supported = TRUE; + + /* Do we have a SASL based authentication mechanism? */ + else if(wordlen > 5 && !memcmp(line, "AUTH=", 5)) { + line += 5; + wordlen -= 5; + + /* Test the word for a matching authentication mechanism */ + if(sasl_mech_equal(line, wordlen, SASL_MECH_STRING_LOGIN)) + imapc->authmechs |= SASL_MECH_LOGIN; + else if(sasl_mech_equal(line, wordlen, SASL_MECH_STRING_PLAIN)) + imapc->authmechs |= SASL_MECH_PLAIN; + else if(sasl_mech_equal(line, wordlen, SASL_MECH_STRING_CRAM_MD5)) + imapc->authmechs |= SASL_MECH_CRAM_MD5; + else if(sasl_mech_equal(line, wordlen, SASL_MECH_STRING_DIGEST_MD5)) + imapc->authmechs |= SASL_MECH_DIGEST_MD5; + else if(sasl_mech_equal(line, wordlen, SASL_MECH_STRING_GSSAPI)) + imapc->authmechs |= SASL_MECH_GSSAPI; + else if(sasl_mech_equal(line, wordlen, SASL_MECH_STRING_EXTERNAL)) + imapc->authmechs |= SASL_MECH_EXTERNAL; + else if(sasl_mech_equal(line, wordlen, SASL_MECH_STRING_NTLM)) + imapc->authmechs |= SASL_MECH_NTLM; + else if(sasl_mech_equal(line, wordlen, SASL_MECH_STRING_XOAUTH2)) + imapc->authmechs |= SASL_MECH_XOAUTH2; + } - if(imapc->ssldone) { - imap_to_imaps(conn); - result = imap_state_capability(conn); + line += wordlen; } } + else if(imapcode == 'O') { + if(data->set.use_ssl && !conn->ssl[FIRSTSOCKET].use) { + /* We don't have a SSL/TLS connection yet, but SSL is requested */ + if(imapc->tls_supported) + /* Switch to TLS connection now */ + result = imap_perform_starttls(conn); + else if(data->set.use_ssl == CURLUSESSL_TRY) + /* Fallback and carry on with authentication */ + result = imap_perform_authenticate(conn); + else { + failf(data, "STARTTLS not supported."); + result = CURLE_USE_SSL_FAILED; + } + } + else + result = imap_perform_authenticate(conn); + } + else + result = imap_perform_login(conn); return result; } -/* For CAPABILITY responses */ -static CURLcode imap_state_capability_resp(struct connectdata *conn, - int imapcode, - imapstate instate) +/* For STARTTLS responses */ +static CURLcode imap_state_starttls_resp(struct connectdata *conn, + int imapcode, + imapstate instate) { CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; (void)instate; /* no use for this yet */ - if(imapcode == 'O') - result = imap_authenticate(conn); + if(imapcode != 'O') { + if(data->set.use_ssl != CURLUSESSL_TRY) { + failf(data, "STARTTLS denied. %c", imapcode); + result = CURLE_USE_SSL_FAILED; + } + else + result = imap_perform_authenticate(conn); + } else - result = imap_state_login(conn); + result = imap_perform_upgrade_tls(conn); return result; } -/* For AUTHENTICATE PLAIN responses */ +/* For AUTHENTICATE PLAIN (without initial response) responses */ static CURLcode imap_state_auth_plain_resp(struct connectdata *conn, int imapcode, imapstate instate) @@ -707,7 +983,7 @@ static CURLcode imap_state_auth_plain_resp(struct connectdata *conn, result = Curl_pp_sendf(&conn->proto.imapc.pp, "%s", plainauth); if(!result) - state(conn, IMAP_AUTHENTICATE); + state(conn, IMAP_AUTHENTICATE_FINAL); } Curl_safefree(plainauth); @@ -717,7 +993,7 @@ static CURLcode imap_state_auth_plain_resp(struct connectdata *conn, return result; } -/* For AUTHENTICATE LOGIN responses */ +/* For AUTHENTICATE LOGIN (without initial response) responses */ static CURLcode imap_state_auth_login_resp(struct connectdata *conn, int imapcode, imapstate instate) @@ -781,7 +1057,7 @@ static CURLcode imap_state_auth_login_password_resp(struct connectdata *conn, result = Curl_pp_sendf(&conn->proto.imapc.pp, "%s", authpasswd); if(!result) - state(conn, IMAP_AUTHENTICATE); + state(conn, IMAP_AUTHENTICATE_FINAL); } Curl_safefree(authpasswd); @@ -836,7 +1112,7 @@ static CURLcode imap_state_auth_cram_resp(struct connectdata *conn, result = Curl_pp_sendf(&conn->proto.imapc.pp, "%s", rplyb64); if(!result) - state(conn, IMAP_AUTHENTICATE); + state(conn, IMAP_AUTHENTICATE_FINAL); } Curl_safefree(rplyb64); @@ -903,10 +1179,10 @@ static CURLcode imap_state_auth_digest_resp_resp(struct connectdata *conn, } else { /* Send an empty response */ - result = Curl_pp_sendf(&conn->proto.imapc.pp, ""); + result = Curl_pp_sendf(&conn->proto.imapc.pp, "%s", ""); if(!result) - state(conn, IMAP_AUTHENTICATE); + state(conn, IMAP_AUTHENTICATE_FINAL); } return result; @@ -914,7 +1190,7 @@ static CURLcode imap_state_auth_digest_resp_resp(struct connectdata *conn, #endif #ifdef USE_NTLM -/* For AUTHENTICATE NTLM responses */ +/* For AUTHENTICATE NTLM (without initial response) responses */ static CURLcode imap_state_auth_ntlm_resp(struct connectdata *conn, int imapcode, imapstate instate) @@ -982,7 +1258,7 @@ static CURLcode imap_state_auth_ntlm_type2msg_resp(struct connectdata *conn, result = Curl_pp_sendf(&conn->proto.imapc.pp, "%s", type3msg); if(!result) - state(conn, IMAP_AUTHENTICATE); + state(conn, IMAP_AUTHENTICATE_FINAL); } Curl_safefree(type3msg); @@ -993,6 +1269,44 @@ static CURLcode imap_state_auth_ntlm_type2msg_resp(struct connectdata *conn, } #endif +/* For AUTH XOAUTH2 (without initial response) responses */ +static CURLcode imap_state_auth_xoauth2_resp(struct connectdata *conn, + int imapcode, + imapstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + size_t len = 0; + char *xoauth = NULL; + + (void)instate; /* no use for this yet */ + + if(imapcode != '+') { + failf(data, "Access denied: %d", imapcode); + result = CURLE_LOGIN_DENIED; + } + else { + /* Create the authorisation message */ + result = Curl_sasl_create_xoauth2_message(conn->data, conn->user, + conn->xoauth2_bearer, + &xoauth, &len); + + /* Send the message */ + if(!result) { + if(xoauth) { + result = Curl_pp_sendf(&conn->proto.imapc.pp, "%s", xoauth); + + if(!result) + state(conn, IMAP_AUTHENTICATE_FINAL); + } + + Curl_safefree(xoauth); + } + } + + return result; +} + /* For final responses to the AUTHENTICATE sequence */ static CURLcode imap_state_auth_final_resp(struct connectdata *conn, int imapcode, @@ -1007,9 +1321,9 @@ static CURLcode imap_state_auth_final_resp(struct connectdata *conn, failf(data, "Authentication failed: %d", imapcode); result = CURLE_LOGIN_DENIED; } - - /* End of connect phase */ - state(conn, IMAP_STOP); + else + /* End of connect phase */ + state(conn, IMAP_STOP); return result; } @@ -1035,97 +1349,112 @@ static CURLcode imap_state_login_resp(struct connectdata *conn, return result; } -/* Start the DO phase */ -static CURLcode imap_select(struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - struct imap_conn *imapc = &conn->proto.imapc; - const char *str = getcmdid(conn); - - result = imap_sendf(conn, str, "%s SELECT %s", str, - imapc->mailbox?imapc->mailbox:""); - if(result) - return result; - - state(conn, IMAP_SELECT); - - return result; -} - -static CURLcode imap_fetch(struct connectdata *conn) +/* For LIST responses */ +static CURLcode imap_state_list_resp(struct connectdata *conn, int imapcode, + imapstate instate) { CURLcode result = CURLE_OK; - const char *str = getcmdid(conn); - - /* TODO: make this select the correct mail - * Use "1 body[text]" to get the full mail body of mail 1 - */ - result = imap_sendf(conn, str, "%s FETCH 1 BODY[TEXT]", str); - if(result) - return result; + char *line = conn->data->state.buffer; + size_t len = strlen(line); - /* - * When issued, the server will respond with a single line similar to - * '* 1 FETCH (BODY[TEXT] {2021}' - * - * Identifying the fetch and how many bytes of contents we can expect. We - * must extract that number before continuing to "download as usual". - */ + (void)instate; /* No use for this yet */ - state(conn, IMAP_FETCH); + if(imapcode == '*') { + /* Temporarily add the LF character back and send as body to the client */ + line[len] = '\n'; + result = Curl_client_write(conn, CLIENTWRITE_BODY, line, len + 1); + line[len] = '\0'; + } + else if(imapcode != 'O') + result = CURLE_QUOTE_ERROR; /* TODO: Fix error code */ + else + /* End of DO phase */ + state(conn, IMAP_STOP); return result; } /* For SELECT responses */ -static CURLcode imap_state_select_resp(struct connectdata *conn, - int imapcode, +static CURLcode imap_state_select_resp(struct connectdata *conn, int imapcode, imapstate instate) { CURLcode result = CURLE_OK; struct SessionHandle *data = conn->data; + struct IMAP *imap = conn->data->req.protop; + struct imap_conn *imapc = &conn->proto.imapc; + const char *line = data->state.buffer; + char tmp[20]; (void)instate; /* no use for this yet */ - if(imapcode != 'O') { + if(imapcode == '*') { + /* See if this is an UIDVALIDITY response */ + if(sscanf(line + 2, "OK [UIDVALIDITY %19[0123456789]]", tmp) == 1) { + Curl_safefree(imapc->mailbox_uidvalidity); + imapc->mailbox_uidvalidity = strdup(tmp); + } + } + else if(imapcode == 'O') { + /* Check if the UIDVALIDITY has been specified and matches */ + if(imap->uidvalidity && imapc->mailbox_uidvalidity && + strcmp(imap->uidvalidity, imapc->mailbox_uidvalidity)) { + failf(conn->data, "Mailbox UIDVALIDITY has changed"); + result = CURLE_REMOTE_FILE_NOT_FOUND; + } + else { + /* Note the currently opened mailbox on this connection */ + imapc->mailbox = strdup(imap->mailbox); + + if(imap->custom) + result = imap_perform_list(conn); + else + result = imap_perform_fetch(conn); + } + } + else { failf(data, "Select failed"); result = CURLE_LOGIN_DENIED; } - else - result = imap_fetch(conn); return result; } -/* For the (first line of) FETCH BODY[TEXT] response */ +/* For the (first line of the) FETCH responses */ static CURLcode imap_state_fetch_resp(struct connectdata *conn, int imapcode, imapstate instate) { CURLcode result = CURLE_OK; struct SessionHandle *data = conn->data; struct imap_conn *imapc = &conn->proto.imapc; - struct FTP *imap = data->state.proto.imap; struct pingpong *pp = &imapc->pp; const char *ptr = data->state.buffer; + bool parsed = FALSE; + curl_off_t size; (void)instate; /* no use for this yet */ - if('*' != imapcode) { + if(imapcode != '*') { Curl_pgrsSetDownloadSize(data, 0); state(conn, IMAP_STOP); - return CURLE_OK; + return CURLE_REMOTE_FILE_NOT_FOUND; /* TODO: Fix error code */ } - /* Something like this comes "* 1 FETCH (BODY[TEXT] {2021}\r" */ + /* Something like this is received "* 1 FETCH (BODY[TEXT] {2021}\r" so parse + the continuation data contained within the curly brackets */ while(*ptr && (*ptr != '{')) ptr++; if(*ptr == '{') { - curl_off_t filesize = curlx_strtoofft(ptr + 1, NULL, 10); - if(filesize) - Curl_pgrsSetDownloadSize(data, filesize); + char *endptr; + size = curlx_strtoofft(ptr + 1, &endptr, 10); + if(endptr - ptr > 1 && endptr[0] == '}' && + endptr[1] == '\r' && endptr[2] == '\0') + parsed = TRUE; + } - infof(data, "Found %" FORMAT_OFF_TU " bytes to download\n", filesize); + if(parsed) { + infof(data, "Found %" FORMAT_OFF_TU " bytes to download\n", size); + Curl_pgrsSetDownloadSize(data, size); if(pp->cache) { /* At this point there is a bunch of data in the header "cache" that is @@ -1133,56 +1462,121 @@ static CURLcode imap_state_fetch_resp(struct connectdata *conn, int imapcode, that there may even be additional "headers" after the body. */ size_t chunk = pp->cache_size; - if(chunk > (size_t)filesize) - /* the conversion from curl_off_t to size_t is always fine here */ - chunk = (size_t)filesize; + if(chunk > (size_t)size) + /* The conversion from curl_off_t to size_t is always fine here */ + chunk = (size_t)size; result = Curl_client_write(conn, CLIENTWRITE_BODY, pp->cache, chunk); if(result) return result; - filesize -= chunk; + data->req.bytecount += chunk; + + infof(data, "Written %" FORMAT_OFF_TU " bytes, %" FORMAT_OFF_TU + " bytes are left for transfer\n", (curl_off_t)chunk, + size - chunk); - /* we've now used parts of or the entire cache */ + /* Have we used the entire cache or just part of it?*/ if(pp->cache_size > chunk) { - /* part of, move the trailing data to the start and reduce the size */ - memmove(pp->cache, pp->cache+chunk, - pp->cache_size - chunk); + /* Only part of it so shrink the cache to fit the trailing data */ + memmove(pp->cache, pp->cache + chunk, pp->cache_size - chunk); pp->cache_size -= chunk; } else { - /* cache is drained */ + /* Free the cache */ Curl_safefree(pp->cache); - pp->cache = NULL; + + /* Reset the cache size */ pp->cache_size = 0; } } - infof(data, "Filesize left: %" FORMAT_OFF_T "\n", filesize); - - if(!filesize) - /* the entire data is already transferred! */ + if(data->req.bytecount == size) + /* The entire data is already transferred! */ Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); - else + else { /* IMAP download */ - Curl_setup_transfer(conn, FIRSTSOCKET, filesize, FALSE, - imap->bytecountp, -1, NULL); /* no upload here */ - - data->req.maxdownload = filesize; + data->req.maxdownload = size; + Curl_setup_transfer(conn, FIRSTSOCKET, size, FALSE, NULL, -1, NULL); + } } - else + else { /* We don't know how to parse this line */ + failf(pp->conn->data, "Failed to parse FETCH response."); result = CURLE_FTP_WEIRD_SERVER_REPLY; /* TODO: fix this code */ + } - /* End of do phase */ + /* End of DO phase */ state(conn, IMAP_STOP); return result; } +/* For final FETCH responses performed after the download */ +static CURLcode imap_state_fetch_final_resp(struct connectdata *conn, + int imapcode, + imapstate instate) +{ + CURLcode result = CURLE_OK; + + (void)instate; /* No use for this yet */ + + if(imapcode != 'O') + result = CURLE_FTP_WEIRD_SERVER_REPLY; /* TODO: Fix error code */ + else + /* End of DONE phase */ + state(conn, IMAP_STOP); + + return result; +} + +/* For APPEND responses */ +static CURLcode imap_state_append_resp(struct connectdata *conn, int imapcode, + imapstate instate) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + + (void)instate; /* No use for this yet */ + + if(imapcode != '+') { + result = CURLE_UPLOAD_FAILED; + } + else { + /* Set the progress upload size */ + Curl_pgrsSetUploadSize(data, data->set.infilesize); + + /* IMAP upload */ + Curl_setup_transfer(conn, -1, -1, FALSE, NULL, FIRSTSOCKET, NULL); + + /* End of DO phase */ + state(conn, IMAP_STOP); + } + + return result; +} + +/* For final APPEND responses performed after the upload */ +static CURLcode imap_state_append_final_resp(struct connectdata *conn, + int imapcode, + imapstate instate) +{ + CURLcode result = CURLE_OK; + + (void)instate; /* No use for this yet */ + + if(imapcode != 'O') + result = CURLE_UPLOAD_FAILED; + else + /* End of DONE phase */ + state(conn, IMAP_STOP); + + return result; +} + static CURLcode imap_statemach_act(struct connectdata *conn) { - CURLcode result; + CURLcode result = CURLE_OK; curl_socket_t sock = conn->sock[FIRSTSOCKET]; int imapcode; struct imap_conn *imapc = &conn->proto.imapc; @@ -1191,32 +1585,39 @@ static CURLcode imap_statemach_act(struct connectdata *conn) /* Busy upgrading the connection; right now all I/O is SSL/TLS, not IMAP */ if(imapc->state == IMAP_UPGRADETLS) - return imap_state_upgrade_tls(conn); + return imap_perform_upgrade_tls(conn); /* Flush any data that needs to be sent */ if(pp->sendleft) return Curl_pp_flushsend(pp); - /* Read the response from the server */ - result = Curl_pp_readresp(sock, pp, &imapcode, &nread); - if(result) - return result; + do { + /* Read the response from the server */ + result = Curl_pp_readresp(sock, pp, &imapcode, &nread); + if(result) + return result; + + /* Was there an error parsing the response line? */ + if(imapcode == -1) + return CURLE_FTP_WEIRD_SERVER_REPLY; + + if(!imapcode) + break; - if(imapcode) { /* We have now received a full IMAP server response */ switch(imapc->state) { case IMAP_SERVERGREET: result = imap_state_servergreet_resp(conn, imapcode, imapc->state); break; - case IMAP_STARTTLS: - result = imap_state_starttls_resp(conn, imapcode, imapc->state); - break; - case IMAP_CAPABILITY: result = imap_state_capability_resp(conn, imapcode, imapc->state); break; + case IMAP_STARTTLS: + result = imap_state_starttls_resp(conn, imapcode, imapc->state); + break; + case IMAP_AUTHENTICATE_PLAIN: result = imap_state_auth_plain_resp(conn, imapcode, imapc->state); break; @@ -1255,7 +1656,11 @@ static CURLcode imap_statemach_act(struct connectdata *conn) break; #endif - case IMAP_AUTHENTICATE: + case IMAP_AUTHENTICATE_XOAUTH2: + result = imap_state_auth_xoauth2_resp(conn, imapcode, imapc->state); + break; + + case IMAP_AUTHENTICATE_FINAL: result = imap_state_auth_final_resp(conn, imapcode, imapc->state); break; @@ -1263,14 +1668,30 @@ static CURLcode imap_statemach_act(struct connectdata *conn) result = imap_state_login_resp(conn, imapcode, imapc->state); break; - case IMAP_FETCH: - result = imap_state_fetch_resp(conn, imapcode, imapc->state); + case IMAP_LIST: + result = imap_state_list_resp(conn, imapcode, imapc->state); break; case IMAP_SELECT: result = imap_state_select_resp(conn, imapcode, imapc->state); break; + case IMAP_FETCH: + result = imap_state_fetch_resp(conn, imapcode, imapc->state); + break; + + case IMAP_FETCH_FINAL: + result = imap_state_fetch_final_resp(conn, imapcode, imapc->state); + break; + + case IMAP_APPEND: + result = imap_state_append_resp(conn, imapcode, imapc->state); + break; + + case IMAP_APPEND_FINAL: + result = imap_state_append_final_resp(conn, imapcode, imapc->state); + break; + case IMAP_LOGOUT: /* fallthrough, just stop! */ default: @@ -1278,7 +1699,7 @@ static CURLcode imap_statemach_act(struct connectdata *conn) state(conn, IMAP_STOP); break; } - } + } while(!result && imapc->state != IMAP_STOP && Curl_pp_moredata(pp)); return result; } @@ -1286,30 +1707,28 @@ static CURLcode imap_statemach_act(struct connectdata *conn) /* Called repeatedly until done from multi.c */ static CURLcode imap_multi_statemach(struct connectdata *conn, bool *done) { + CURLcode result = CURLE_OK; struct imap_conn *imapc = &conn->proto.imapc; - CURLcode result; - if((conn->handler->flags & PROTOPT_SSL) && !imapc->ssldone) + if((conn->handler->flags & PROTOPT_SSL) && !imapc->ssldone) { result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, &imapc->ssldone); - else - result = Curl_pp_multi_statemach(&imapc->pp); + if(result || !imapc->ssldone) + return result; + } + result = Curl_pp_statemach(&imapc->pp, FALSE); *done = (imapc->state == IMAP_STOP) ? TRUE : FALSE; return result; } -static CURLcode imap_easy_statemach(struct connectdata *conn) +static CURLcode imap_block_statemach(struct connectdata *conn) { - struct imap_conn *imapc = &conn->proto.imapc; - struct pingpong *pp = &imapc->pp; CURLcode result = CURLE_OK; + struct imap_conn *imapc = &conn->proto.imapc; - while(imapc->state != IMAP_STOP) { - result = Curl_pp_easy_statemach(pp); - if(result) - break; - } + while(imapc->state != IMAP_STOP && !result) + result = Curl_pp_statemach(&imapc->pp, TRUE); return result; } @@ -1318,68 +1737,67 @@ static CURLcode imap_easy_statemach(struct connectdata *conn) required */ static CURLcode imap_init(struct connectdata *conn) { + CURLcode result = CURLE_OK; struct SessionHandle *data = conn->data; - struct FTP *imap = data->state.proto.imap; - - if(!imap) { - imap = data->state.proto.imap = calloc(sizeof(struct FTP), 1); - if(!imap) - return CURLE_OUT_OF_MEMORY; - } + struct IMAP *imap; - /* Get some initial data into the imap struct */ - imap->bytecountp = &data->req.bytecount; + imap = data->req.protop = calloc(sizeof(struct IMAP), 1); + if(!imap) + result = CURLE_OUT_OF_MEMORY; - /* No need to duplicate user+password, the connectdata struct won't change - during a session, but we re-init them here since on subsequent inits - since the conn struct may have changed or been replaced. - */ - imap->user = conn->user; - imap->passwd = conn->passwd; + return result; +} - return CURLE_OK; +/* For the IMAP "protocol connect" and "doing" phases only */ +static int imap_getsock(struct connectdata *conn, curl_socket_t *socks, + int numsocks) +{ + return Curl_pp_getsock(&conn->proto.imapc.pp, socks, numsocks); } /*********************************************************************** * - * imap_connect() should do everything that is to be considered a part of - * the connection phase. + * imap_connect() + * + * This function should do everything that is to be considered a part of the + * connection phase. * * The variable 'done' points to will be TRUE if the protocol-layer connect - * phase is done when this function returns, or FALSE is not. When called as - * a part of the easy interface, it will always be TRUE. + * phase is done when this function returns, or FALSE if not. */ static CURLcode imap_connect(struct connectdata *conn, bool *done) { - CURLcode result; + CURLcode result = CURLE_OK; struct imap_conn *imapc = &conn->proto.imapc; struct pingpong *pp = &imapc->pp; *done = FALSE; /* default to not done yet */ - /* If there already is a protocol-specific struct allocated for this - sessionhandle, deal with it */ - Curl_reset_reqproto(conn); - - result = imap_init(conn); - if(CURLE_OK != result) - return result; - - /* We always support persistent connections on imap */ + /* We always support persistent connections in IMAP */ conn->bits.close = FALSE; - pp->response_time = RESP_TIMEOUT; /* set default response time-out */ + /* Set the default response time-out */ + pp->response_time = RESP_TIMEOUT; pp->statemach_act = imap_statemach_act; pp->endofresp = imap_endofresp; pp->conn = conn; - Curl_pp_init(pp); /* init generic pingpong data */ + /* Set the default preferred authentication mechanism */ + imapc->prefmech = SASL_AUTH_ANY; + + /* Initialise the pingpong layer */ + Curl_pp_init(pp); + + /* Parse the URL options */ + result = imap_parse_url_options(conn); + if(result) + return result; /* Start off waiting for the server greeting response */ state(conn, IMAP_SERVERGREET); - /* Start off with an id of '*' */ - imapc->idstr = "*"; + /* Start off with an response id of '*' */ + strcpy(imapc->resptag, "*"); result = imap_multi_statemach(conn, done); @@ -1398,26 +1816,55 @@ static CURLcode imap_connect(struct connectdata *conn, bool *done) static CURLcode imap_done(struct connectdata *conn, CURLcode status, bool premature) { + CURLcode result = CURLE_OK; struct SessionHandle *data = conn->data; - struct FTP *imap = data->state.proto.imap; - CURLcode result=CURLE_OK; + struct IMAP *imap = data->req.protop; (void)premature; if(!imap) - /* When the easy handle is removed from the multi while libcurl is still - * trying to resolve the host name, it seems that the imap struct is not - * yet initialized, but the removal action calls Curl_done() which calls - * this function. So we simply return success if no imap pointer is set. - */ + /* When the easy handle is removed from the multi interface while libcurl + is still trying to resolve the host name, the IMAP struct is not yet + initialized. However, the removal action calls Curl_done() which in + turn calls this function, so we simply return success. */ return CURLE_OK; if(status) { conn->bits.close = TRUE; /* marked for closure */ result = status; /* use the already set error code */ } + else if(!data->set.connect_only && !imap->custom && + (imap->uid || data->set.upload)) { + /* Handle responses after FETCH or APPEND transfer has finished */ + if(!data->set.upload) + state(conn, IMAP_FETCH_FINAL); + else { + /* End the APPEND command first by sending an empty line */ + result = Curl_pp_sendf(&conn->proto.imapc.pp, "%s", ""); + if(!result) + state(conn, IMAP_APPEND_FINAL); + } + + /* Run the state-machine + + TODO: when the multi interface is used, this _really_ should be using + the imap_multi_statemach function but we have no general support for + non-blocking DONE operations, not in the multi state machine and with + Curl_done() invokes on several places in the code! + */ + if(!result) + result = imap_block_statemach(conn); + } - /* Clear the transfer mode for the next connection */ + /* Cleanup our per-request based variables */ + Curl_safefree(imap->mailbox); + Curl_safefree(imap->uidvalidity); + Curl_safefree(imap->uid); + Curl_safefree(imap->section); + Curl_safefree(imap->custom); + Curl_safefree(imap->custom_params); + + /* Clear the transfer mode for the next request */ imap->transfer = FTPTRANSFER_BODY; return result; @@ -1427,31 +1874,57 @@ static CURLcode imap_done(struct connectdata *conn, CURLcode status, * * imap_perform() * - * This is the actual DO function for IMAP. Get a file/directory according to - * the options previously setup. + * This is the actual DO function for IMAP. Fetch or append a message, or do + * other things according to the options previously setup. */ static CURLcode imap_perform(struct connectdata *conn, bool *connected, bool *dophase_done) { /* This is IMAP and no proxy */ CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + struct IMAP *imap = data->req.protop; + struct imap_conn *imapc = &conn->proto.imapc; + bool selected = FALSE; DEBUGF(infof(conn->data, "DO phase starts\n")); if(conn->data->set.opt_no_body) { /* Requested no body means no transfer */ - struct FTP *imap = conn->data->state.proto.imap; imap->transfer = FTPTRANSFER_INFO; } *dophase_done = FALSE; /* not done yet */ + /* Determine if the requested mailbox (with the same UIDVALIDITY if set) + has already been selected on this connection */ + if(imap->mailbox && imapc->mailbox && + !strcmp(imap->mailbox, imapc->mailbox) && + (!imap->uidvalidity || !imapc->mailbox_uidvalidity || + !strcmp(imap->uidvalidity, imapc->mailbox_uidvalidity))) + selected = TRUE; + /* Start the first command in the DO phase */ - result = imap_select(conn); + if(conn->data->set.upload) + /* APPEND can be executed directly */ + result = imap_perform_append(conn); + else if(imap->custom && (selected || !imap->mailbox)) + /* Custom command using the same mailbox or no mailbox */ + result = imap_perform_list(conn); + else if(!imap->custom && selected && imap->uid) + /* FETCH from the same mailbox */ + result = imap_perform_fetch(conn); + else if(imap->mailbox && !selected && (imap->custom || imap->uid)) + /* SELECT the mailbox */ + result = imap_perform_select(conn); + else + /* LIST */ + result = imap_perform_list(conn); + if(result) return result; - /* run the state-machine */ + /* Run the state-machine */ result = imap_multi_statemach(conn, dophase_done); *connected = conn->bits.tcpconnect[FIRSTSOCKET]; @@ -1473,52 +1946,21 @@ static CURLcode imap_perform(struct connectdata *conn, bool *connected, */ static CURLcode imap_do(struct connectdata *conn, bool *done) { - CURLcode retcode = CURLE_OK; + CURLcode result = CURLE_OK; *done = FALSE; /* default to false */ - /* - Since connections can be re-used between SessionHandles, this might be a - connection already existing but on a fresh SessionHandle struct so we must - make sure we have a good 'struct IMAP' to play with. For new connections, - the struct IMAP is allocated and setup in the imap_connect() function. - */ - Curl_reset_reqproto(conn); - retcode = imap_init(conn); - if(retcode) - return retcode; - /* Parse the URL path */ - retcode = imap_parse_url_path(conn); - if(retcode) - return retcode; - - retcode = imap_regular_transfer(conn, done); - - return retcode; -} - -/*********************************************************************** - * - * imap_logout() - * - * This should be called before calling sclose(). We should then wait for the - * response from the server before returning. The calling code should then try - * to close the connection. - * - */ -static CURLcode imap_logout(struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - const char *str = getcmdid(conn); - - result = imap_sendf(conn, str, "%s LOGOUT", str, NULL); + result = imap_parse_url_path(conn); if(result) return result; - state(conn, IMAP_LOGOUT); + /* Parse the custom request */ + result = imap_parse_custom_request(conn); + if(result) + return result; - result = imap_easy_statemach(conn); + result = imap_regular_transfer(conn, done); return result; } @@ -1532,16 +1974,17 @@ static CURLcode imap_logout(struct connectdata *conn) */ static CURLcode imap_disconnect(struct connectdata *conn, bool dead_connection) { - struct imap_conn *imapc= &conn->proto.imapc; + struct imap_conn *imapc = &conn->proto.imapc; /* We cannot send quit unconditionally. If this connection is stale or bad in any way, sending quit and waiting around here will make the - disconnect wait in vain and cause more problems than we need to */ + disconnect wait in vain and cause more problems than we need to. */ /* The IMAP session may or may not have been allocated/setup at this point! */ if(!dead_connection && imapc->pp.conn) - (void)imap_logout(conn); /* ignore errors on the LOGOUT */ + if(!imap_perform_logout(conn)) + (void)imap_block_statemach(conn); /* ignore errors on LOGOUT */ /* Disconnect from the server */ Curl_pp_disconnect(&imapc->pp); @@ -1551,35 +1994,15 @@ static CURLcode imap_disconnect(struct connectdata *conn, bool dead_connection) /* Cleanup our connection based variables */ Curl_safefree(imapc->mailbox); + Curl_safefree(imapc->mailbox_uidvalidity); return CURLE_OK; } -/*********************************************************************** - * - * imap_parse_url_path() - * - * Parse the URL path into separate path components. - * - */ -static CURLcode imap_parse_url_path(struct connectdata *conn) -{ - /* The imap struct is already inited in imap_connect() */ - struct imap_conn *imapc = &conn->proto.imapc; - struct SessionHandle *data = conn->data; - const char *path = data->state.path; - - if(!*path) - path = "INBOX"; - - /* URL decode the path and use this mailbox */ - return Curl_urldecode(data, path, 0, &imapc->mailbox, NULL, TRUE); -} - /* Call this when the DO phase has completed */ static CURLcode imap_dophase_done(struct connectdata *conn, bool connected) { - struct FTP *imap = conn->data->state.proto.imap; + struct IMAP *imap = conn->data->req.protop; (void)connected; @@ -1597,12 +2020,10 @@ static CURLcode imap_doing(struct connectdata *conn, bool *dophase_done) if(result) DEBUGF(infof(conn->data, "DO phase failed\n")); - else { - if(*dophase_done) { - result = imap_dophase_done(conn, FALSE /* not connected */); + else if(*dophase_done) { + result = imap_dophase_done(conn, FALSE /* not connected */); - DEBUGF(infof(conn->data, "DO phase is complete\n")); - } + DEBUGF(infof(conn->data, "DO phase is complete\n")); } return result; @@ -1627,30 +2048,33 @@ static CURLcode imap_regular_transfer(struct connectdata *conn, /* Make sure size is unknown at this point */ data->req.size = -1; + /* Set the progress data */ Curl_pgrsSetUploadCounter(data, 0); Curl_pgrsSetDownloadCounter(data, 0); Curl_pgrsSetUploadSize(data, 0); Curl_pgrsSetDownloadSize(data, 0); + /* Carry out the perform */ result = imap_perform(conn, &connected, dophase_done); - if(CURLE_OK == result) { - if(!*dophase_done) - /* The DO phase has not completed yet */ - return CURLE_OK; - + /* Perform post DO phase operations if necessary */ + if(!result && *dophase_done) result = imap_dophase_done(conn, connected); - } return result; } -static CURLcode imap_setup_connection(struct connectdata * conn) +static CURLcode imap_setup_connection(struct connectdata *conn) { struct SessionHandle *data = conn->data; + /* Initialise the IMAP layer */ + CURLcode result = imap_init(conn); + if(result) + return result; + if(conn->bits.httpproxy && !data->set.tunnel_thru_httpproxy) { - /* Unless we have asked to tunnel imap operations through the proxy, we + /* Unless we have asked to tunnel IMAP operations through the proxy, we switch and use HTTP operations only */ #ifndef CURL_DISABLE_HTTP if(conn->handler == &Curl_handler_imap) @@ -1664,10 +2088,8 @@ static CURLcode imap_setup_connection(struct connectdata * conn) #endif } - /* We explicitly mark this connection as persistent here as we're doing - IMAP over HTTP and thus we accidentally avoid setting this value - otherwise */ - conn->bits.close = FALSE; + /* set it up as an HTTP connection instead */ + return conn->handler->setup_connection(conn); #else failf(data, "IMAP over http proxy requires HTTP support built-in!"); return CURLE_UNSUPPORTED_PROTOCOL; @@ -1679,4 +2101,352 @@ static CURLcode imap_setup_connection(struct connectdata * conn) return CURLE_OK; } +/*********************************************************************** + * + * imap_sendf() + * + * Sends the formated string as an IMAP command to the server. + * + * Designed to never block. + */ +static CURLcode imap_sendf(struct connectdata *conn, const char *fmt, ...) +{ + CURLcode result = CURLE_OK; + struct imap_conn *imapc = &conn->proto.imapc; + char *taggedfmt; + va_list ap; + + DEBUGASSERT(fmt); + + /* Calculate the next command ID wrapping at 3 digits */ + imapc->cmdid = (imapc->cmdid + 1) % 1000; + + /* Calculate the tag based on the connection ID and command ID */ + snprintf(imapc->resptag, sizeof(imapc->resptag), "%c%03d", + 'A' + curlx_sltosi(conn->connection_id % 26), imapc->cmdid); + + /* Prefix the format with the tag */ + taggedfmt = aprintf("%s %s", imapc->resptag, fmt); + if(!taggedfmt) + return CURLE_OUT_OF_MEMORY; + + /* Send the data with the tag */ + va_start(ap, fmt); + result = Curl_pp_vsendf(&imapc->pp, taggedfmt, ap); + va_end(ap); + + Curl_safefree(taggedfmt); + + return result; +} + +/*********************************************************************** + * + * imap_atom() + * + * Checks the input string for characters that need escaping and returns an + * atom ready for sending to the server. + * + * The returned string needs to be freed. + * + */ +static char *imap_atom(const char *str) +{ + const char *p1; + char *p2; + size_t backsp_count = 0; + size_t quote_count = 0; + bool space_exists = FALSE; + size_t newlen = 0; + char *newstr = NULL; + + if(!str) + return NULL; + + /* Count any unescapped characters */ + p1 = str; + while(*p1) { + if(*p1 == '\\') + backsp_count++; + else if(*p1 == '"') + quote_count++; + else if(*p1 == ' ') + space_exists = TRUE; + + p1++; + } + + /* Does the input contain any unescapped characters? */ + if(!backsp_count && !quote_count && !space_exists) + return strdup(str); + + /* Calculate the new string length */ + newlen = strlen(str) + backsp_count + quote_count + (space_exists ? 2 : 0); + + /* Allocate the new string */ + newstr = (char *) malloc((newlen + 1) * sizeof(char)); + if(!newstr) + return NULL; + + /* Surround the string in quotes if necessary */ + p2 = newstr; + if(space_exists) { + newstr[0] = '"'; + newstr[newlen - 1] = '"'; + p2++; + } + + /* Copy the string, escaping backslash and quote characters along the way */ + p1 = str; + while(*p1) { + if(*p1 == '\\' || *p1 == '"') { + *p2 = '\\'; + p2++; + } + + *p2 = *p1; + + p1++; + p2++; + } + + /* Terminate the string */ + newstr[newlen] = '\0'; + + return newstr; +} + +/*********************************************************************** + * + * imap_is_bchar() + * + * Portable test of whether the specified char is a "bchar" as defined in the + * grammar of RFC-5092. + */ +static bool imap_is_bchar(char ch) +{ + switch(ch) { + /* bchar */ + case ':': case '@': case '/': + /* bchar -> achar */ + case '&': case '=': + /* bchar -> achar -> uchar -> unreserved */ + case '0': case '1': case '2': case '3': case '4': case '5': case '6': + case '7': case '8': case '9': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': + case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': + case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': + case 'V': case 'W': case 'X': case 'Y': case 'Z': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': + case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': + case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': + case 'v': case 'w': case 'x': case 'y': case 'z': + case '-': case '.': case '_': case '~': + /* bchar -> achar -> uchar -> sub-delims-sh */ + case '!': case '$': case '\'': case '(': case ')': case '*': + case '+': case ',': + /* bchar -> achar -> uchar -> pct-encoded */ + case '%': /* HEXDIG chars are already included above */ + return true; + + default: + return false; + } +} + +/*********************************************************************** + * + * imap_parse_url_options() + * + * Parse the URL login options. + */ +static CURLcode imap_parse_url_options(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct imap_conn *imapc = &conn->proto.imapc; + const char *options = conn->options; + const char *ptr = options; + + if(options) { + const char *key = ptr; + + while(*ptr && *ptr != '=') + ptr++; + + if(strnequal(key, "AUTH", 4)) { + const char *value = ptr + 1; + + if(strequal(value, "*")) + imapc->prefmech = SASL_AUTH_ANY; + else if(strequal(value, SASL_MECH_STRING_LOGIN)) + imapc->prefmech = SASL_MECH_LOGIN; + else if(strequal(value, SASL_MECH_STRING_PLAIN)) + imapc->prefmech = SASL_MECH_PLAIN; + else if(strequal(value, SASL_MECH_STRING_CRAM_MD5)) + imapc->prefmech = SASL_MECH_CRAM_MD5; + else if(strequal(value, SASL_MECH_STRING_DIGEST_MD5)) + imapc->prefmech = SASL_MECH_DIGEST_MD5; + else if(strequal(value, SASL_MECH_STRING_GSSAPI)) + imapc->prefmech = SASL_MECH_GSSAPI; + else if(strequal(value, SASL_MECH_STRING_NTLM)) + imapc->prefmech = SASL_MECH_NTLM; + else if(strequal(value, SASL_MECH_STRING_XOAUTH2)) + imapc->prefmech = SASL_MECH_XOAUTH2; + else + imapc->prefmech = SASL_AUTH_NONE; + } + else + result = CURLE_URL_MALFORMAT; + } + + return result; +} + +/*********************************************************************** + * + * imap_parse_url_path() + * + * Parse the URL path into separate path components. + * + */ +static CURLcode imap_parse_url_path(struct connectdata *conn) +{ + /* The imap struct is already initialised in imap_connect() */ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + struct IMAP *imap = data->req.protop; + const char *begin = data->state.path; + const char *ptr = begin; + + /* See how much of the URL is a valid path and decode it */ + while(imap_is_bchar(*ptr)) + ptr++; + + if(ptr != begin) { + /* Remove the trailing slash if present */ + const char *end = ptr; + if(end > begin && end[-1] == '/') + end--; + + result = Curl_urldecode(data, begin, end - begin, &imap->mailbox, NULL, + TRUE); + if(result) + return result; + } + else + imap->mailbox = NULL; + + /* There can be any number of parameters in the form ";NAME=VALUE" */ + while(*ptr == ';') { + char *name; + char *value; + size_t valuelen; + + /* Find the length of the name parameter */ + begin = ++ptr; + while(*ptr && *ptr != '=') + ptr++; + + if(!*ptr) + return CURLE_URL_MALFORMAT; + + /* Decode the name parameter */ + result = Curl_urldecode(data, begin, ptr - begin, &name, NULL, TRUE); + if(result) + return result; + + /* Find the length of the value parameter */ + begin = ++ptr; + while(imap_is_bchar(*ptr)) + ptr++; + + /* Decode the value parameter */ + result = Curl_urldecode(data, begin, ptr - begin, &value, &valuelen, TRUE); + if(result) { + Curl_safefree(name); + return result; + } + + DEBUGF(infof(conn->data, "IMAP URL parameter '%s' = '%s'\n", name, value)); + + /* Process the known hierarchical parameters (UIDVALIDITY, UID and SECTION) + stripping of the trailing slash character if it is present. + + Note: Unknown parameters trigger a URL_MALFORMAT error. */ + if(Curl_raw_equal(name, "UIDVALIDITY") && !imap->uidvalidity) { + if(valuelen > 0 && value[valuelen - 1] == '/') + value[valuelen - 1] = '\0'; + + imap->uidvalidity = value; + value = NULL; + } + else if(Curl_raw_equal(name, "UID") && !imap->uid) { + if(valuelen > 0 && value[valuelen - 1] == '/') + value[valuelen - 1] = '\0'; + + imap->uid = value; + value = NULL; + } + else if(Curl_raw_equal(name, "SECTION") && !imap->section) { + if(valuelen > 0 && value[valuelen - 1] == '/') + value[valuelen - 1] = '\0'; + + imap->section = value; + value = NULL; + } + else { + Curl_safefree(name); + Curl_safefree(value); + + return CURLE_URL_MALFORMAT; + } + + Curl_safefree(name); + Curl_safefree(value); + } + + /* Any extra stuff at the end of the URL is an error */ + if(*ptr) + return CURLE_URL_MALFORMAT; + + return CURLE_OK; +} + +/*********************************************************************** + * + * imap_parse_custom_request() + * + * Parse the custom request. + */ +static CURLcode imap_parse_custom_request(struct connectdata *conn) +{ + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + struct IMAP *imap = data->req.protop; + const char *custom = data->set.str[STRING_CUSTOMREQUEST]; + + if(custom) { + /* URL decode the custom request */ + result = Curl_urldecode(data, custom, 0, &imap->custom, NULL, TRUE); + + /* Extract the parameters if specified */ + if(!result) { + const char *params = imap->custom; + + while(*params && *params != ' ') + params++; + + if(*params) { + imap->custom_params = strdup(params); + imap->custom[params - imap->custom] = '\0'; + + if(!imap->custom_params) + result = CURLE_OUT_OF_MEMORY; + } + } + } + + return result; +} + #endif /* CURL_DISABLE_IMAP */ |