#ifndef ESP_MAIL_IMAP_H #define ESP_MAIL_IMAP_H #include "ESP_Mail_Client_Version.h" #if !VALID_VERSION_CHECK(30409) #error "Mixed versions compilation." #endif /** * Mail Client Arduino Library for Espressif's ESP32 and ESP8266, Raspberry Pi RP2040 Pico, and SAMD21 with u-blox NINA-W102 WiFi/Bluetooth module * * Created August 28, 2023 * * This library allows Espressif's ESP32, ESP8266, SAMD and RP2040 Pico devices to send and read Email through the SMTP and IMAP servers. * * The MIT License (MIT) * Copyright (c) 2023 K. Suwatchai (Mobizt) * * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of * the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "ESP_Mail_Client_Version.h" #include "ESP_Mail_Client.h" #if defined(ENABLE_IMAP) #if defined(MB_ARDUINO_PICO) extern uint8_t _FS_start; extern uint8_t _FS_end; #endif int ESP_Mail_Client::decodeChar(const char *s) { return 16 * hexval(*(s + 1)) + hexval(*(s + 2)); } void ESP_Mail_Client::decodeQP_UTF8(const char *buf, char *out) { char *tmp = strP(esp_mail_str_47 /* "0123456789ABCDEF" */); int idx = 0; while (*buf) { if (*buf != '=') out[idx++] = *buf++; else if (*(buf + 1) == '\r' && *(buf + 2) == '\n') buf += 3; else if (*(buf + 1) == '\n') buf += 2; else if (!strchr(tmp, *(buf + 1))) out[idx++] = *buf++; else if (!strchr(tmp, *(buf + 2))) out[idx++] = *buf++; else { out[idx++] = decodeChar(buf); buf += 3; } } // release memory freeMem(&tmp); } char *ESP_Mail_Client::decode7Bit_UTF8(char *buf) { MB_String s; // only non NULL and 7-bit ASCII are allowed // rfc2045 section 2.7 size_t len = buf ? strlen(buf) : 0; for (size_t i = 0; i < len; i++) { if (buf[i] > 0 && buf[i] < 128 && i < 998) s.append(1, buf[i]); } // some special chars can't send in 7bit unless encoded as queoted printable string char *decoded = allocMem(s.length() + 10); decodeQP_UTF8(s.c_str(), decoded); s.clear(); return decoded; } char *ESP_Mail_Client::decode8Bit_UTF8(char *buf) { MB_String s; // only non NULL and less than 998 octet length are allowed // rfc2045 section 2.8 size_t len = buf ? strlen(buf) : 0; for (size_t i = 0; i < len; i++) { if (buf[i] > 0 && i < 998) s.append(1, buf[i]); } char *decoded = allocMem(s.length() + 1); strcpy(decoded, s.c_str()); s.clear(); return decoded; } void ESP_Mail_Client::decodeString(IMAPSession *imap, MB_String &str, const char *enc) { size_t p1 = 0, p2 = 0; MB_String headerEnc; if (strlen(enc) == 0) { while (str[p1] == ' ' && p1 < str.length() - 1) p1++; if (str[p1] == '=' && str[p1 + 1] == '?') { p2 = str.find('?', p1 + 2); if (p2 != MB_String::npos) headerEnc = str.substr(p1 + 2, p2 - p1 - 2); } } else headerEnc = enc; int bufSize = str.length() + 10; char *buf = allocMem(bufSize); // Content Q and B decodings RFC2047Decoder.decode(mbfs, buf, str.c_str(), bufSize); // Char set decoding esp_mail_char_decoding_scheme scheme = getEncodingFromCharset(headerEnc.c_str()); if (imap->_charDecCallback) { IMAP_Decoding_Info decoding; decoding.charset = headerEnc.c_str(); decoding.data = buf; decoding.type = IMAP_Decoding_Info::message_part_type_header; imap->_charDecCallback(&decoding); if (decoding.decodedString.length() > 0) { char *buf2 = allocMem(decoding.decodedString.length() + 1); strcpy(buf2, decoding.decodedString.c_str()); // release memory and point to new buffer freeMem(&buf); buf = buf2; } } else if (scheme == esp_mail_char_decoding_scheme_iso8859_1) { int len = strlen(buf); int olen = (len + 1) * 2; unsigned char *out = allocMem(olen); decodeLatin1_UTF8(out, &olen, (unsigned char *)buf, &len); // release memory and point to new buffer freeMem(&buf); buf = (char *)out; } else if (scheme == esp_mail_char_decoding_scheme_tis_620 || scheme == esp_mail_char_decoding_scheme_iso8859_11 || scheme == esp_mail_char_decoding_scheme_windows_874) { size_t len2 = strlen(buf); char *tmp = allocMem((len2 + 1) * 3); decodeTIS620_UTF8(tmp, buf, len2); // release memory and point to new buffer freeMem(&buf); buf = tmp; } str = buf; // release memory freeMem(&buf); } esp_mail_char_decoding_scheme ESP_Mail_Client::getEncodingFromCharset(const char *enc) { esp_mail_char_decoding_scheme scheme = esp_mail_char_decoding_scheme_default; for (int i = esp_mail_char_decoding_utf8; i < esp_mail_char_decoding_maxType; i++) { if (strpos(enc, char_decodings[i].text, 0, false) > -1) scheme = (esp_mail_char_decoding_scheme)i; } return scheme; } int ESP_Mail_Client::encodeUnicode_UTF8(char *out, uint32_t utf) { if (utf <= 0x7F) { // Plain ASCII out[0] = (char)utf; out[1] = 0; return 1; } else if (utf <= 0x07FF) { // 2-byte unicode out[0] = (char)(((utf >> 6) & 0x1F) | 0xC0); out[1] = (char)(((utf >> 0) & 0x3F) | 0x80); out[2] = 0; return 2; } else if (utf <= 0xFFFF) { // 3-byte unicode out[0] = (char)(((utf >> 12) & 0x0F) | 0xE0); out[1] = (char)(((utf >> 6) & 0x3F) | 0x80); out[2] = (char)(((utf >> 0) & 0x3F) | 0x80); out[3] = 0; return 3; } else if (utf <= 0x10FFFF) { // 4-byte unicode out[0] = (char)(((utf >> 18) & 0x07) | 0xF0); out[1] = (char)(((utf >> 12) & 0x3F) | 0x80); out[2] = (char)(((utf >> 6) & 0x3F) | 0x80); out[3] = (char)(((utf >> 0) & 0x3F) | 0x80); out[4] = 0; return 4; } else { // error - use replacement character out[0] = (char)0xEF; out[1] = (char)0xBF; out[2] = (char)0xBD; out[3] = 0; return 0; } } void ESP_Mail_Client::decodeTIS620_UTF8(char *out, const char *in, size_t len) { // output is the 3-byte value UTF-8 int j = 0; for (size_t i = 0; i < len; i++) { if (in[i] < 0x80) out[j++] = in[i]; else if ((in[i] >= 0xa0 && in[i] < 0xdb) || (in[i] > 0xde && in[i] < 0xfc)) { int unicode = 0x0e00 + in[i] - 0xa0; char o[5]; memset(o, 0, 5); int r = encodeUnicode_UTF8(o, unicode); for (int x = 0; x < r; x++) out[j++] = o[x]; } } } int ESP_Mail_Client::decodeLatin1_UTF8(unsigned char *out, int *outlen, const unsigned char *in, int *inlen) { unsigned char *outstart = out; const unsigned char *base = in; const unsigned char *processed = in; unsigned char *outend = out + *outlen; const unsigned char *inend; unsigned int c; int bits; inend = in + (*inlen); while ((in < inend) && (out - outstart + 5 < *outlen)) { c = *in++; /* assertion: c is a single UTF-4 value */ if (out >= outend) break; if (c < 0x80) { *out++ = c; bits = -6; } else { *out++ = ((c >> 6) & 0x1F) | 0xC0; bits = 0; } for (; bits >= 0; bits -= 6) { if (out >= outend) break; *out++ = ((c >> bits) & 0x3F) | 0x80; } processed = (const unsigned char *)in; } *outlen = out - outstart; *inlen = processed - base; return (0); } bool ESP_Mail_Client::sendFetchCommand(IMAPSession *imap, int msgIndex, esp_mail_imap_command cmdCase) { MB_String cmd, cmd2, cmd3; appendHeadersFetchCommand(imap, cmd, msgIndex, false); if (cmdCase == esp_mail_imap_cmd_fetch_body_mime) { joinStringDot(cmd2, 2, imap_commands[esp_mail_imap_command_header].text, imap_commands[esp_mail_imap_command_fields].text); appendSpace(cmd2); joinStringSpace(cmd3, false, 2, message_headers[esp_mail_message_header_field_content_type].text, message_headers[esp_mail_message_header_field_content_transfer_encoding].text); appendString(cmd2, cmd3.c_str(), false, false, esp_mail_string_mark_type_round_bracket); } else if (cmdCase == esp_mail_imap_cmd_fetch_body_text) cmd2 = cPart(imap)->partNumFetchStr.length() > 0 ? cPart(imap)->partNumFetchStr.c_str() : imap_commands[esp_mail_imap_command_text].text; else if (cmdCase == esp_mail_imap_cmd_fetch_body_attachment) cmd2 = cPart(imap)->partNumFetchStr; if (cmd2.length() > 0) appendString(cmd, cmd2.c_str(), false, false, esp_mail_string_mark_type_square_bracket); bool allowPartialFetch = (cmdCase == esp_mail_imap_cmd_fetch_body_attachment && cPart(imap)->is_firmware_file) ? false : true; if (imap->_mimeDataStreamCallback) allowPartialFetch = false; if (allowPartialFetch) { // Apply partial fetch in case download was disabled. if (!imap->_storageReady && imap->_attDownload && cmdCase == esp_mail_imap_cmd_fetch_body_attachment) cmd += esp_mail_str_48; /* "<0.0>" */ // This case should not happen because the memory storage was previousely checked. else if ((!imap->_msgDownload && cmdCase == esp_mail_imap_cmd_fetch_body_text) || (imap->_msgDownload && !imap->_storageReady)) { cmd += esp_mail_str_49; /* "<0." */ cmd += imap->_imap_data->limit.msg_size; cmd += esp_mail_str_20; /* ">" */ } } if (imapSend(imap, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; return true; } bool ESP_Mail_Client::readMail(IMAPSession *imap, bool closeSession) { if (!imap || !sessionExisted(imap)) return false; imap->checkUID(); imap->checkPath(); imap->_cbData._success = false; if (!imap->connected()) imap->_mailboxOpened = false; imap->_isFirmwareUpdated = false; MB_String buf, command, _uid; size_t readCount = 0; imap->_multipart_levels.clear(); if (!reconnect(imap)) return false; imap->_msgDownload = imap->_imap_data->download.text || imap->_imap_data->download.html; imap->_attDownload = imap->_imap_data->download.attachment || imap->_imap_data->download.inlineImg; if (!imap->_storageChecked) { imap->_storageChecked = true; imap->_storageReady = imap->_imap_data->download.header || (!imap->_imap_data->fetch.headerOnly && (imap->_msgDownload || imap->_attDownload)) ? mbfs->checkStorageReady(mbfs_type imap->_imap_data->storage.type) : true; } bool readyToDownload = (imap->_msgDownload || imap->_attDownload) && imap->_storageReady; if (!imap->_storageReady) sendStorageNotReadyError(imap, imap->_imap_data->storage.type); #if defined(MB_ARDUINO_ESP) || defined(MB_ARDUINO_PICO) int cmem = MailClient.getFreeHeap(); if (cmem < ESP_MAIL_MIN_MEM) { #if !defined(SILENT_MODE) if (imap->_debug && imap->_statusCallback && !imap->_customCmdResCallback) { esp_mail_debug_print(); errorStatusCB(imap, nullptr, MAIL_CLIENT_ERROR_OUT_OF_MEMORY, true); } #endif goto out; } #endif if (!imap->connected() && !imap->_loginStatus) { #if !defined(SILENT_MODE) if (imap->_debug && imap->_statusCallback && !imap->_customCmdResCallback) { esp_mail_debug_print(); errorStatusCB(imap, nullptr, MAIL_CLIENT_ERROR_NOT_YET_LOGIN, true); } #endif return false; } // new session if (!imap->connected()) { // authenticate new bool ssl = false; if (!imap->connect(ssl)) { closeTCPSession(imap); return false; } if (!imapAuth(imap, ssl)) { closeTCPSession(imap); return false; } } else { // If time config changed, we will update time MailClient.prepareTime(imap->_session_cfg, imap); // reuse session for (size_t i = 0; i < imap->_headers.size(); i++) imap->_headers[i].part_headers.clear(); imap->_headers.clear(); if (imap->_imap_data->fetch.sequence_set.string.length() > 0 || imap->_imap_data->fetch.uid.length() > 0 || imap->_imap_data->fetch.number.length() > 0) imap->_headerOnly = false; else imap->_headerOnly = true; } imap->_rfc822_part_count = 0; imap->_mbif._availableItems = 0; imap->_imap_msg_num.clear(); imap->_uidSearch = false; imap->_mbif._searchCount = 0; if (imap->_currentFolder.length() == 0) return handleIMAPError(imap, IMAP_STATUS_NO_MAILBOX_FOLDER_OPENED, false); if (!imap->_mailboxOpened || (imap->_imap_data->fetch.set_seen && !imap->_headerOnly && imap->_readOnlyMode)) { if (!imap->openFolder(imap->_currentFolder.c_str(), imap->_readOnlyMode && !imap->_imap_data->fetch.set_seen)) return handleIMAPError(imap, IMAP_STATUS_OPEN_MAILBOX_FAILED, false); } if (imap->_headerOnly) { if (imap->_imap_data->search.criteria.length() > 0) { command = esp_mail_imap_tag_str; #if !defined(SILENT_MODE) printDebug(imap, esp_mail_cb_str_18 /* "Searching messages..." */, esp_mail_dbg_str_36 /* "searching messages" */, esp_mail_debug_tag_type_client, true, false); #endif if (strposP(imap->_imap_data->search.criteria.c_str(), imap_cmd_post_tokens[esp_mail_imap_command_uid].c_str(), 0) != -1) { imap->_uidSearch = true; command += imap_cmd_pre_tokens[esp_mail_imap_command_uid]; } command += imap_cmd_pre_tokens[esp_mail_imap_command_search]; imap->_imap_data->search.criteria.trim(); MB_String tag; appendTagSpace(tag); // Remove internal used reserved tag if (strpos(imap->_imap_data->search.criteria.c_str(), tag.c_str(), 0, true) == 0) imap->_imap_data->search.criteria.erase(0, tag.length()); for (size_t i = 0; i < imap->_imap_data->search.criteria.length(); i++) { if (imap->_imap_data->search.criteria[i] != ' ' && imap->_imap_data->search.criteria[i] != '\r' && imap->_imap_data->search.criteria[i] != '\n') buf.append(1, imap->_imap_data->search.criteria[i]); if (imap->_imap_data->search.criteria[i] == ' ') { if ((imap->_uidSearch && strcmp(buf.c_str(), imap_commands[esp_mail_imap_command_uid].text) == 0) || (imap->_unseen && buf.find(imap_commands[esp_mail_imap_command_all].text) != MB_String::npos)) buf.clear(); if (strcmp(buf.c_str(), imap_commands[esp_mail_imap_command_search].text) != 0 && buf.length() > 0) prependSpace(command, buf.c_str()); buf.clear(); } } if (imap->_unseen && strpos(imap->_imap_data->search.criteria.c_str(), imap_cmd_pre_tokens[esp_mail_imap_command_new].c_str(), 0) == -1) command += imap_cmd_pre_tokens[esp_mail_imap_command_new]; if (buf.length() > 0) prependSpace(command, buf.c_str()); if (!imap->isModseqSupported() && strpos(imap->_imap_data->search.criteria.c_str(), imap_cmd_pre_tokens[esp_mail_imap_command_modsec].c_str(), 0, false) != -1) { imap->_responseStatus.errorCode = IMAP_STATUS_MODSEQ_WAS_NOT_SUPPORTED; #if !defined(SILENT_MODE) if (imap->_statusCallback) sendErrorCB(imap, imap->errorReason().c_str(), false, false); if (imap->_debug) esp_mail_debug_print_tag(imap->errorReason().c_str(), esp_mail_debug_tag_type_error, true); #endif return false; } if (imapSend(imap, command.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; command.clear(); imap->_imap_cmd = esp_mail_imap_cmd_search; if (!handleIMAPResponse(imap, IMAP_STATUS_BAD_COMMAND, closeSession)) return false; #if !defined(SILENT_MODE) if (imap->_statusCallback) { callBackSendNewLine(imap, false); if (imap->_imap_msg_num.size() > 0) { int bufLen = 100; char *buf = allocMem(bufLen); snprintf(buf, bufLen, pgm2Str(esp_mail_str_50 /* "Search limit: %d\nFound %d messages\nShow %d messages\n" */), (int)imap->_imap_data->limit.search, imap->_mbif._searchCount, (int)imap->_imap_msg_num.size()); sendCallback(imap, buf, false, false); // release memory freeMem(&buf); } else sendCallback(imap, esp_mail_error_imap_str_9 /* "no messages found for the specified search criteria" */, false, false); } #endif } else { #if !defined(SILENT_MODE) if (imap->_statusCallback) sendCallback(imap, esp_mail_error_imap_str_10 /* "no search criteria provided, then fetching the latest message" */, false, false); #endif imap->_mbif._availableItems++; esp_mail_imap_msg_num_t msg_num; msg_num.type = esp_mail_imap_msg_num_type_number; msg_num.value = (uint32_t)imap->_mbif._msgCount; imap->_imap_msg_num.push_back(msg_num); imap->_headerOnly = false; imap->_imap_data->fetch.number = imap->_mbif._msgCount; } } else { if (imap->_imap_data->fetch.sequence_set.string.length() > 0) { imap->_headerOnly = imap->_imap_data->fetch.sequence_set.headerOnly; imap->mFetchSequenceSet(); imap->_mbif._availableItems = imap->_imap_msg_num.size(); } else { if (imap->_imap_data->fetch.uid.length() > 0) { imap->_mbif._availableItems++; esp_mail_imap_msg_num_t msg_num; msg_num.type = esp_mail_imap_msg_num_type_uid; msg_num.value = (uint32_t)atoi(imap->_imap_data->fetch.uid.c_str()); imap->_imap_msg_num.push_back(msg_num); } if (imap->_imap_data->fetch.number.length() > 0) { imap->_mbif._availableItems++; esp_mail_imap_msg_num_t msg_num; msg_num.type = esp_mail_imap_msg_num_type_number; msg_num.value = (uint32_t)atoi(imap->_imap_data->fetch.number.c_str()); imap->_imap_msg_num.push_back(msg_num); } } } if (imap->_imap_data->fetch.headerOnly) imap->_headerOnly = true; for (size_t i = 0; i < imap->_imap_msg_num.size(); i++) { imap->_cMsgIdx = i; imap->_totalRead++; #if defined(MB_ARDUINO_ESP) || defined(MB_ARDUINO_PICO) if (MailClient.getFreeHeap() - (imap->_imap_data->limit.msg_size * (i + 1)) < ESP_MAIL_MIN_MEM) { errorStatusCB(imap, nullptr, MAIL_CLIENT_ERROR_OUT_OF_MEMORY, true); goto out; } #endif #if !defined(SILENT_MODE) if (imap->_statusCallback) { readCount++; int bufLen = 100; PGM_P p = imap->_uidSearch || imap->_imap_msg_num[i].type == esp_mail_imap_msg_num_type_uid ? esp_mail_str_52 /* "Fetch message %d, UID: %d" */ : esp_mail_str_53 /* "Fetch message %d, Number: %d" */; char *buf = allocMem(bufLen); snprintf(buf, bufLen, pgm2Str(p), imap->_totalRead, (int)imap->_imap_msg_num[i].value); sendCallback(imap, buf, true, false); // release memory freeMem(&buf); } if (imap->_debug) esp_mail_debug_print_tag(esp_mail_dbg_str_37 /* "send IMAP command, FETCH" */, esp_mail_debug_tag_type_client, true); #endif MB_String cmd; appendHeadersFetchCommand(imap, cmd, i, true); // We fetch only known RFC822 headers because // using Fetch RFC822.HEADER reurns all included unused headers // which required more memory and network bandwidth. MB_String cmd2; appendRFC822HeadersFetchCommand(cmd2); appendString(cmd, cmd2.c_str(), false, false, esp_mail_string_mark_type_square_bracket); imap->addModifier(cmd, esp_mail_imap_command_changedsince, imap->_imap_data->fetch.modsequence); if (imapSend(imap, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) { if (i < imap->_imap_msg_num.size() - 1) continue; return false; } imap->_imap_cmd = esp_mail_imap_cmd_fetch_body_header; int err = imap->_headerOnly ? IMAP_STATUS_IMAP_RESPONSE_FAILED : IMAP_STATUS_BAD_COMMAND; if (!handleIMAPResponse(imap, err, closeSession)) { if (i < imap->_imap_msg_num.size() - 1) continue; return false; } if (!cHeader(imap)) continue; if (imap->_imap_msg_num[i].type == esp_mail_imap_msg_num_type_number) cHeader(imap)->message_uid = imap->mGetUID(cHeader(imap)->message_no); cHeader(imap)->flags = imap->getFlags(cHeader(imap)->message_no); if (!imap->_headerOnly) { imap->_cPartIdx = 0; // Reset attachment state if it was set by "multipart/mixed" content type header cHeader(imap)->hasAttachment = false; #if !defined(SILENT_MODE) if (imap->_debug) debugPrintNewLine(); #endif // multipart if (cHeader(imap)->multipart) { struct esp_mail_imap_multipart_level_t mlevel; mlevel.level = 1; mlevel.fetch_rfc822_header = false; mlevel.append_body_text = false; imap->_multipart_levels.push_back(mlevel); if (!fetchMultipartBodyHeader(imap, i)) return false; } else { // single part if (imap->_debug) printBodyPartFechingDubug(imap, "1", false); cHeader(imap)->partNumStr.clear(); if (!sendFetchCommand(imap, i, esp_mail_imap_cmd_fetch_body_mime)) return false; imap->_imap_cmd = esp_mail_imap_cmd_fetch_body_mime; if (!handleIMAPResponse(imap, IMAP_STATUS_BAD_COMMAND, closeSession)) return false; } if (readyToDownload && imap->_imap_data->storage.saved_path.length() == 0) imap->_imap_data->storage.saved_path = esp_mail_str_10; /* "/" */ if (cHeader(imap)->part_headers.size() > 0) { cHeader(imap)->sd_alias_file_count = 0; imap->_sdFileList.clear(); if (!mbfs->longNameSupported()) imap->_sdFileList = esp_mail_str_40; /* "[" */ #if !defined(SILENT_MODE) if (cHeader(imap)->attachment_count > 0 && imap->_statusCallback) { int bufLen = 100; char *buf = allocMem(bufLen); snprintf(buf, bufLen, pgm2Str(esp_mail_str_54 /* "Attachments (%d)" */), cHeader(imap)->attachment_count); callBackSendNewLine(imap, false); sendCallback(imap, buf, false, false); // release memory freeMem(&buf); int count = 0; for (size_t j = 0; j < cHeader(imap)->part_headers.size(); j++) { imap->_cPartIdx = j; if (!cPart(imap)->rfc822_part && cPart(imap)->attach_type != esp_mail_att_type_none) { count++; MB_String str = count; appendDot(str); prependSpace(str, cPart(imap)->filename.c_str()); sendCallback(imap, str.c_str(), false, false); } } } #endif MB_String s1, s2; int _idx1 = 0; for (size_t j = 0; j < cHeader(imap)->part_headers.size(); j++) { imap->_cPartIdx = j; if (cPart(imap)->rfc822_part) { s1 = cPart(imap)->partNumStr; _idx1 = cPart(imap)->rfc822_msg_Idx; } else if (s1.length() > 0) { if (multipartMember(s1, cPart(imap)->partNumStr)) { cPart(imap)->message_sub_type = esp_mail_imap_message_sub_type_rfc822; cPart(imap)->rfc822_msg_Idx = _idx1; } } if (cPart(imap)->multipart_sub_type == esp_mail_imap_multipart_sub_type_parallel) s2 = cPart(imap)->partNumStr; else if (s2.length() > 0) { if (multipartMember(s2, cPart(imap)->partNumStr)) { cPart(imap)->attach_type = esp_mail_att_type_attachment; if (cPart(imap)->filename.length() == 0) { if (cPart(imap)->name.length() > 0) cPart(imap)->filename = cPart(imap)->name; else { char *uid = getRandomUID(); cPart(imap)->filename = uid; cPart(imap)->filename += mimeinfo[esp_mail_file_extension_dat].endsWith; freeMem(&uid); } } } } checkFirmwareFile(imap, cPart(imap)->filename.c_str(), *cPart(imap), true); } int attach_count = 0; int ccnt = 0; for (size_t j = 0; j < cHeader(imap)->part_headers.size(); j++) { imap->_cPartIdx = j; if (cPart(imap)->rfc822_part || cPart(imap)->multipart_sub_type != esp_mail_imap_multipart_sub_type_none) continue; bool rfc822_body_subtype = cPart(imap)->message_sub_type == esp_mail_imap_message_sub_type_rfc822 && cPart(imap)->attach_type != esp_mail_att_type_attachment; if (cPart(imap)->attach_type == esp_mail_att_type_none && (cPart(imap)->msg_type == esp_mail_msg_type_html || cPart(imap)->msg_type == esp_mail_msg_type_plain || cPart(imap)->msg_type == esp_mail_msg_type_enriched)) { bool ret = ((imap->_imap_data->enable.rfc822 || imap->_imap_data->download.rfc822) && rfc822_body_subtype) || (!rfc822_body_subtype && ((imap->_imap_data->enable.text && (cPart(imap)->msg_type == esp_mail_msg_type_plain || cPart(imap)->msg_type == esp_mail_msg_type_enriched)) || (imap->_imap_data->enable.html && cPart(imap)->msg_type == esp_mail_msg_type_html) || (cPart(imap)->msg_type == esp_mail_msg_type_html && imap->_imap_data->download.html) || ((cPart(imap)->msg_type == esp_mail_msg_type_plain || cPart(imap)->msg_type == esp_mail_msg_type_enriched) && imap->_imap_data->download.text))); if (!ret) continue; #if !defined(SILENT_MODE) if ((imap->_imap_data->download.rfc822 && rfc822_body_subtype) || (!rfc822_body_subtype && ((cPart(imap)->msg_type == esp_mail_msg_type_html && imap->_imap_data->download.html) || ((cPart(imap)->msg_type == esp_mail_msg_type_plain || cPart(imap)->msg_type == esp_mail_msg_type_enriched) && imap->_imap_data->download.text)))) { if (ccnt == 0 && imap->_statusCallback) sendCallback(imap, esp_mail_cb_str_43 /* "Downloading messages..." */, true, false); if (imap->_debug) { debugPrintNewLine(); if (cPart(imap)->msg_type == esp_mail_msg_type_plain || cPart(imap)->msg_type == esp_mail_msg_type_enriched) esp_mail_debug_print_tag(esp_mail_dbg_str_74 /* "download plain TEXT message" */, esp_mail_debug_tag_type_client, true); else if (cPart(imap)->msg_type == esp_mail_msg_type_html) esp_mail_debug_print_tag(esp_mail_dbg_str_71 /* "download HTML message" */, esp_mail_debug_tag_type_client, true); } } else { if (ccnt == 0) sendCallback(imap, esp_mail_cb_str_28 /* "Reading messages..." */, true, false); if (imap->_debug) { debugPrintNewLine(); if (cPart(imap)->msg_type == esp_mail_msg_type_plain || cPart(imap)->msg_type == esp_mail_msg_type_enriched) esp_mail_debug_print_tag(esp_mail_dbg_str_46 /* "reading plain TEXT message" */, esp_mail_debug_tag_type_client, true); else if (cPart(imap)->msg_type == esp_mail_msg_type_html) esp_mail_debug_print_tag(esp_mail_dbg_str_47 /* "reading HTML message" */, esp_mail_debug_tag_type_client, true); } } #endif ccnt++; if (!sendFetchCommand(imap, i, esp_mail_imap_cmd_fetch_body_text)) return false; imap->_imap_cmd = esp_mail_imap_cmd_fetch_body_text; if (!handleIMAPResponse(imap, IMAP_STATUS_IMAP_RESPONSE_FAILED, closeSession)) return false; } else if (cPart(imap)->attach_type != esp_mail_att_type_none && (imap->_storageReady || cPart(imap)->is_firmware_file)) { if (cPart(imap)->is_firmware_file || (imap->_imap_data->download.attachment && cPart(imap)->attach_type == esp_mail_att_type_attachment) || (imap->_imap_data->download.inlineImg && cPart(imap)->attach_type == esp_mail_att_type_inline)) { #if !defined(SILENT_MODE) if (cPart(imap)->save_to_file) { if (attach_count == 0 && imap->_statusCallback) sendCallback(imap, esp_mail_cb_str_19 /* "Downloading attachments..." */, true, false); if (imap->_debug) { debugPrintNewLine(); int bufLen = 100; char *buf = allocMem(bufLen); snprintf(buf, bufLen, pgm2Str(esp_mail_dbg_str_70 /* "download attachment %d of %d" */), attach_count + 1, (int)cHeader(imap)->attachment_count); esp_mail_debug_print_tag(buf, esp_mail_debug_tag_type_client, true); // release memory freeMem(&buf); MB_String filePath = imap->_imap_data->storage.saved_path; filePath += esp_mail_str_10; /* "/" */ filePath += cHeader(imap)->message_uid; filePath += esp_mail_str_10; /* "/" */ filePath += cPart(imap)->filename; esp_mail_debug_print_tag(filePath.c_str(), esp_mail_debug_tag_type_client, true); } } #endif attach_count++; if (cPart(imap)->octetLen <= (int)imap->_imap_data->limit.attachment_size) { if (imap->_storageReady || cPart(imap)->is_firmware_file) { if ((int)j < (int)cHeader(imap)->part_headers.size() - 1) if (cHeader(imap)->part_headers[j + 1].octetLen > (int)imap->_imap_data->limit.attachment_size) cHeader(imap)->downloaded_bytes += cHeader(imap)->part_headers[j + 1].octetLen; if (!sendFetchCommand(imap, i, esp_mail_imap_cmd_fetch_body_attachment)) return false; imap->_imap_cmd = esp_mail_imap_cmd_fetch_body_attachment; if (!handleIMAPResponse(imap, IMAP_STATUS_IMAP_RESPONSE_FAILED, closeSession)) return false; yield_impl(); } } else { if ((int)j == (int)cHeader(imap)->part_headers.size() - 1) cHeader(imap)->downloaded_bytes += cPart(imap)->octetLen; } } } } } if (imap->_storageReady && imap->_imap_data->download.header && !imap->_headerSaved) { #if !defined(SILENT_MODE) if (imap->_statusCallback) sendCallback(imap, esp_mail_cb_str_21 /* "Saving message header to file..." */, true, false); else if (imap->_debug) debugPrintNewLine(); #endif saveHeader(imap, false); saveHeader(imap, true); } // save files list to file if (imap->_storageReady && imap->_sdFileList.length() > 0) { MB_String filepath = cHeader(imap)->message_uid; filepath += mimeinfo[esp_mail_file_extension_txt].endsWith; if (mbfs->open(filepath, mbfs_type imap->_imap_data->storage.type, mb_fs_open_mode_write) > -1) { mbfs->print(mbfs_type imap->_imap_data->storage.type, imap->_sdFileList.c_str()); mbfs->close(mbfs_type imap->_imap_data->storage.type); } } imap->_cMsgIdx++; } #if !defined(SILENT_MODE) if (imap->_debug) { MB_String str = esp_mail_str_55; /* "Free Heap: " */ str += MailClient.getFreeHeap(); esp_mail_debug_print_tag(str.c_str(), esp_mail_debug_tag_type_client, true); } #endif } #if defined(MB_ARDUINO_ESP) || defined(MB_ARDUINO_PICO) out: #endif if (readCount < imap->_imap_msg_num.size()) { imap->_mbif._availableItems = readCount; imap->_imap_msg_num.erase(imap->_imap_msg_num.begin() + readCount, imap->_imap_msg_num.end()); } if (closeSession) { if (!imap->closeSession()) return false; } else { #if !defined(SILENT_MODE) printDebug(imap, esp_mail_cb_str_46 /* "Finished reading Email" */, esp_mail_dbg_str_29 /* "finished reading Email" */, esp_mail_debug_tag_type_client, true, false); #endif } imap->_cbData._success = true; #if !defined(SILENT_MODE) if (imap->_statusCallback) callBackSendNewLine(imap, true); #endif return true; } void ESP_Mail_Client::appendHeadersFetchCommand(IMAPSession *imap, MB_String &cmd, int index, bool debug) { if (imap->_uidSearch || imap->_imap_msg_num[index].type == esp_mail_imap_msg_num_type_uid) appendSpace(cmd, true, 2, imap_commands[esp_mail_imap_command_uid].text, imap_commands[esp_mail_imap_command_fetch].text); else appendSpace(cmd, true, imap_commands[esp_mail_imap_command_fetch].text); #if !defined(SILENT_MODE) if (debug && imap->_debug) esp_mail_debug_print_tag(esp_mail_dbg_str_26 /* "fetch message header" */, esp_mail_debug_tag_type_client, true); #endif joinStringSpace(cmd, false, 2, MB_String((int)imap->_imap_msg_num[index].value).c_str(), imap_commands[esp_mail_imap_command_body].text); if (!imap->_imap_data->fetch.set_seen) prependDot(cmd, imap_commands[esp_mail_imap_command_peek].text); } void ESP_Mail_Client::appendRFC822HeadersFetchCommand(MB_String &cmd) { joinStringDot(cmd, 2, imap_commands[esp_mail_imap_command_header].text, imap_commands[esp_mail_imap_command_fields].text); appendSpace(cmd); MB_String cmd2; for (int i = 0; i < esp_mail_rfc822_header_field_maxType; i++) appendSpace(cmd2, false, rfc822_headers[i].text); joinStringSpace(cmd2, false, 4, message_headers[esp_mail_message_header_field_content_type].text, message_headers[esp_mail_message_header_field_content_transfer_encoding].text, message_headers[esp_mail_message_header_field_content_language].text, message_headers[esp_mail_message_header_field_accept_language].text); appendString(cmd, cmd2.c_str(), false, false, esp_mail_string_mark_type_round_bracket); } bool ESP_Mail_Client::getMultipartFechCmd(IMAPSession *imap, int msgIdx, MB_String &partText) { if (imap->_multipart_levels.size() == 0) return false; int cLevel = imap->_multipart_levels.size() - 1; cHeader(imap)->partNumStr.clear(); appendHeadersFetchCommand(imap, partText, msgIdx, false); MB_String cmd1; for (size_t i = 0; i < imap->_multipart_levels.size(); i++) { if (i > 0) { cmd1 += esp_mail_str_27; /* "." */ cHeader(imap)->partNumStr += esp_mail_str_27; /* "." */ } cmd1 += imap->_multipart_levels[i].level; cHeader(imap)->partNumStr += imap->_multipart_levels[i].level; } if (imap->_multipart_levels[cLevel].fetch_rfc822_header) { MB_String cmd2; appendRFC822HeadersFetchCommand(cmd2); prependDot(cmd1, cmd2.c_str()); imap->_multipart_levels[cLevel].append_body_text = true; } else prependDot(cmd1, imap_commands[esp_mail_imap_command_mime].text); appendString(partText, cmd1.c_str(), false, false, esp_mail_string_mark_type_square_bracket); imap->_multipart_levels[cLevel].fetch_rfc822_header = false; return true; } bool ESP_Mail_Client::multipartMember(const MB_String &parent, const MB_String &child) { if (parent.length() > child.length()) return false; for (size_t i = 0; i < parent.length(); i++) if (parent[i] != child[i]) return false; return true; } bool ESP_Mail_Client::fetchMultipartBodyHeader(IMAPSession *imap, int msgIdx) { bool ret = true; if (!connected(imap)) { closeTCPSession(imap); return false; } int cLevel = 0; // slower than BODYSTRUCTURE parsing but sure do { #if defined(MB_ARDUINO_ESP) || defined(MB_ARDUINO_PICO) // Prevent stack overflow if (MailClient.getFreeHeap() < ESP_MAIL_MIN_MEM) { errorStatusCB(imap, nullptr, MAIL_CLIENT_ERROR_OUT_OF_MEMORY, true); break; } #endif struct esp_mail_message_part_info_t *_cpart = &cHeader(imap)->part_headers[cHeader(imap)->message_data_count - 1]; bool rfc822_body_subtype = _cpart->message_sub_type == esp_mail_imap_message_sub_type_rfc822 && _cpart->attach_type != esp_mail_att_type_attachment; MB_String cmd; if (!getMultipartFechCmd(imap, msgIdx, cmd)) return true; if (imap->_debug) printBodyPartFechingDubug(imap, cHeader(imap)->partNumStr.c_str(), imap->_multipart_levels.size() > 1); // Try fetching the part and its sub parts hierarchically // Some sub part may not exist at the current multipart level if (imapSend(imap, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; imap->_imap_cmd = esp_mail_imap_cmd_fetch_body_mime; ret = handleIMAPResponse(imap, IMAP_STATUS_IMAP_RESPONSE_FAILED, false); _cpart = &cHeader(imap)->part_headers[cHeader(imap)->message_data_count - 1]; rfc822_body_subtype = _cpart->message_sub_type == esp_mail_imap_message_sub_type_rfc822 && _cpart->attach_type != esp_mail_att_type_attachment; cLevel = imap->_multipart_levels.size() - 1; if (ret) { if (_cpart->multipart) { if (_cpart->multipart_sub_type == esp_mail_imap_multipart_sub_type_parallel || _cpart->multipart_sub_type == esp_mail_imap_multipart_sub_type_alternative || _cpart->multipart_sub_type == esp_mail_imap_multipart_sub_type_related || _cpart->multipart_sub_type == esp_mail_imap_multipart_sub_type_mixed) { struct esp_mail_imap_multipart_level_t mlevel; mlevel.level = 1; mlevel.fetch_rfc822_header = false; mlevel.append_body_text = false; imap->_multipart_levels.push_back(mlevel); fetchMultipartBodyHeader(imap, msgIdx); } else imap->_multipart_levels[cLevel].level++; } else { if (rfc822_body_subtype) { // Get additional rfc822 message header imap->_multipart_levels[cLevel].fetch_rfc822_header = true; fetchMultipartBodyHeader(imap, msgIdx); } else { if (imap->_multipart_levels[cLevel].append_body_text) { // single part rfc822 message body, append TEXT to the body fetch command prependDot(_cpart->partNumFetchStr, imap_commands[esp_mail_imap_command_text].text); imap->_multipart_levels[cLevel].append_body_text = false; } imap->_multipart_levels[cLevel].level++; } } } } while (ret); imap->_multipart_levels.pop_back(); if (imap->_multipart_levels.size() > 0) { cLevel = imap->_multipart_levels.size() - 1; imap->_multipart_levels[cLevel].level++; } return true; } void ESP_Mail_Client::printBodyPartFechingDubug(IMAPSession *imap, const char *partNum, bool multiLevel) { #if !defined(SILENT_MODE) MB_String str = multiLevel ? esp_mail_dbg_str_28 /* "fetch body sub part header, " */ : esp_mail_dbg_str_27; /* "fetch body part header, " */ str += partNum; esp_mail_debug_print_tag(str.c_str(), esp_mail_debug_tag_type_client, true); #endif } bool ESP_Mail_Client::imapAuth(IMAPSession *imap, bool &ssl) { if (!sessionExisted(imap)) return false; imap->_auth_capability[esp_mail_auth_capability_login] = false; imap->_session_cfg->int_start_tls = imap->_session_cfg->secure.startTLS; imap->_session_cfg->int_mode = imap->_session_cfg->secure.mode; #if !defined(ESP_MAIL_DISABLE_SSL) unauthenticate: #endif // capabilities may change after TLS negotiation if (!imap->checkCapabilities()) return false; #if !defined(ESP_MAIL_DISABLE_SSL) if (imap->_session_cfg->int_mode != esp_mail_secure_mode_nonsecure) { // start TLS when needed or the server issues if ((imap->_auth_capability[esp_mail_auth_capability_starttls] || imap->_session_cfg->int_start_tls || imap->_session_cfg->int_mode == esp_mail_secure_mode_ssl_tls) && !ssl) { #if !defined(SILENT_MODE) printDebug(imap, esp_mail_cb_str_2 /* "Sending STARTTLS command..." */, esp_mail_dbg_str_1 /* "send command, STARTTLS" */, esp_mail_debug_tag_type_client, true, false); #endif imapSend(imap, imap->prependTag(imap_commands[esp_mail_imap_command_starttls].text).c_str(), true); // rfc2595 section 3.1 imap->_imap_cmd = esp_mail_imap_cmd_starttls; if (!handleIMAPResponse(imap, IMAP_STATUS_BAD_COMMAND, false)) return false; #if !defined(SILENT_MODE) if (imap->_debug) esp_mail_debug_print_tag(esp_mail_dbg_str_22 /* "perform SSL/TLS handshake" */, esp_mail_debug_tag_type_client, true); #endif // connect in secure mode // do TLS handshake if (!imap->client.connectSSL(imap->_session_cfg->certificate.verify)) return handleIMAPError(imap, MAIL_CLIENT_ERROR_SSL_TLS_STRUCTURE_SETUP, false); // set the secure mode imap->_session_cfg->int_start_tls = false; imap->_session_cfg->int_mode = esp_mail_secure_mode_undefined; ssl = true; imap->_secure = true; // check the capabilitiy again to prevent the man in the middle attack goto unauthenticate; } } #endif imap->clearMessageData(); imap->_mailboxOpened = false; bool creds = imap->_session_cfg->login.email.length() > 0 && imap->_session_cfg->login.password.length() > 0; bool sasl_auth_oauth = imap->_session_cfg->login.accessToken.length() > 0 && imap->_auth_capability[esp_mail_auth_capability_xoauth2]; bool sasl_login = creds; bool sasl_auth_plain = imap->_auth_capability[esp_mail_auth_capability_plain] && creds; bool supported_sasl = sasl_auth_oauth || sasl_login || sasl_auth_plain; if (!supported_sasl) return handleIMAPError(imap, IMAP_STATUS_NO_SUPPORTED_AUTH, false); // rfc4959 if (supported_sasl) { #if !defined(SILENT_MODE) if (imap->_statusCallback) sendCallback(imap, esp_mail_cb_str_14 /* "Logging in..." */, true, false); else if (imap->_debug) debugPrintNewLine(); #endif } if (sasl_auth_oauth) { if (!imap->_auth_capability[esp_mail_auth_capability_xoauth2]) { handleIMAPError(imap, IMAP_STATUS_SERVER_OAUTH2_LOGIN_DISABLED, false); return false; } #if !defined(SILENT_MODE) if (imap->_debug) esp_mail_debug_print_tag(esp_mail_dbg_str_45 /* "send IMAP command, AUTH XOAUTH2" */, esp_mail_debug_tag_type_client, true); #endif MB_String cmd; joinStringSpace(cmd, true, 2, imap_commands[esp_mail_imap_command_authenticate].text, imap_commands[esp_mail_imap_command_xoauth2].text); if (imap->_auth_capability[esp_mail_auth_capability_sasl_ir]) { prependSpace(cmd, getXOAUTH2String(imap->_session_cfg->login.email, imap->_session_cfg->login.accessToken).c_str()); if (imapSend(imap, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; } else { if (imapSend(imap, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; imap->_imap_cmd = esp_mail_imap_cmd_sasl_auth_oauth; if (!handleIMAPResponse(imap, IMAP_STATUS_AUTHENTICATE_FAILED, true)) return false; cmd = getXOAUTH2String(imap->_session_cfg->login.email, imap->_session_cfg->login.accessToken); if (imapSend(imap, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; } imap->_imap_cmd = esp_mail_imap_cmd_sasl_auth_oauth; if (!handleIMAPResponse(imap, IMAP_STATUS_AUTHENTICATE_FAILED, false)) return false; } else if (sasl_auth_plain) { #if !defined(SILENT_MODE) if (imap->_debug) esp_mail_debug_print_tag(esp_mail_dbg_str_44 /* "send IMAP command, AUTHENTICATE PLAIN" */, esp_mail_debug_tag_type_client, true); #endif int len = imap->_session_cfg->login.email.length() + imap->_session_cfg->login.password.length() + 2; uint8_t *tmp = allocMem(len); memset(tmp, 0, len); int p = 1; memcpy(tmp + p, imap->_session_cfg->login.email.c_str(), imap->_session_cfg->login.email.length()); p += imap->_session_cfg->login.email.length() + 1; memcpy(tmp + p, imap->_session_cfg->login.password.c_str(), imap->_session_cfg->login.password.length()); p += imap->_session_cfg->login.password.length(); MB_String cmd; joinStringSpace(cmd, true, 2, imap_commands[esp_mail_imap_command_authenticate].text, imap_commands[esp_mail_imap_command_plain].text); if (imap->_auth_capability[esp_mail_auth_capability_sasl_ir]) { prependSpace(cmd, encodeBase64Str(tmp, p).c_str()); // release memory freeMem(&tmp); if (imapSend(imap, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; } else { if (imapSend(imap, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; imap->_imap_cmd = esp_mail_imap_cmd_sasl_auth_plain; if (!handleIMAPResponse(imap, IMAP_STATUS_AUTHENTICATE_FAILED, true)) return false; cmd = encodeBase64Str(tmp, p); // release memory freeMem(&tmp); if (imapSend(imap, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; } imap->_imap_cmd = esp_mail_imap_cmd_sasl_auth_plain; if (!handleIMAPResponse(imap, IMAP_STATUS_AUTHENTICATE_FAILED, true)) return false; } else if (sasl_login) { #if !defined(SILENT_MODE) if (imap->_debug) esp_mail_debug_print_tag(esp_mail_dbg_str_34 /* "send IMAP command, LOGIN" */, esp_mail_debug_tag_type_client, true); #endif MB_String cmd; joinStringSpace(cmd, true, 3, imap_commands[esp_mail_imap_command_login].text, imap->_session_cfg->login.email.c_str(), imap->_session_cfg->login.password.c_str()); if (imapSend(imap, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; imap->_imap_cmd = esp_mail_imap_cmd_sasl_login; if (!handleIMAPResponse(imap, IMAP_STATUS_AUTHENTICATE_FAILED, true)) return false; } // auto capabilities after login? if (!imap->_feature_capability[esp_mail_imap_read_capability_auto_caps]) { if (!imap->checkCapabilities()) return false; } if (imap->_feature_capability[esp_mail_imap_read_capability_id]) { if (!imap->id(&imap->_imap_data->identification)) return false; } if (supported_sasl) imap->_authenticated = true; return true; } bool ESP_Mail_Client::imapLogout(IMAPSession *imap) { #if defined(ESP8266) return false; #endif if (!sessionExisted(imap)) return false; #if !defined(SILENT_MODE) printDebug(imap, esp_mail_cb_str_20 /* "Logging out..." */, esp_mail_dbg_str_38 /* "send IMAP command, LOGOUT" */, esp_mail_debug_tag_type_client, true, false); #endif if (imapSend(imap, imap->prependTag(imap_commands[esp_mail_imap_command_logout].text).c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; imap->_imap_cmd = esp_mail_imap_cmd_logout; if (!handleIMAPResponse(imap, IMAP_STATUS_BAD_COMMAND, false)) return false; imap->_authenticated = false; #if !defined(SILENT_MODE) printDebug(imap, esp_mail_cb_str_47 /* "Log out completed" */, esp_mail_dbg_str_31 /* "log out completed" */, esp_mail_debug_tag_type_client, true, false); #endif return true; } size_t ESP_Mail_Client::imapSend(IMAPSession *imap, PGM_P data, bool newline) { if (!imap || !sessionReady(imap)) return 0; int sent = 0; MB_String s = data; int toSend = newline ? s.length() + 2 : s.length(); if (imap->_debug && imap->_debugLevel > esp_mail_debug_level_maintainer && !imap->_customCmdResCallback) esp_mail_debug_print(s.c_str(), newline); sent = newline ? imap->client.println(s.c_str()) : imap->client.print(s.c_str()); if (sent != toSend) { errorStatusCB(imap, nullptr, sent, true); sent = 0; } return sent; } size_t ESP_Mail_Client::imapSend(IMAPSession *imap, int data, bool newline) { MB_String s = data; return imapSend(imap, s.c_str(), newline); } size_t ESP_Mail_Client::imapSend(IMAPSession *imap, uint8_t *data, size_t size) { if (!imap || !sessionReady(imap)) return 0; int sent = 0; sent = imap->client.write(data, size); if (sent != (int)size) { errorStatusCB(imap, nullptr, sent, true); sent = 0; } return sent; } bool ESP_Mail_Client::mSetFlag(IMAPSession *imap, MB_StringPtr sequenceSet, MB_StringPtr flag, esp_mail_imap_store_flag_type type, bool closeSession, bool silent, bool UID, int32_t modsequence) { if (!reconnect(imap)) return false; if (!imap->connected()) { imap->_mailboxOpened = false; return false; } if (imap->_currentFolder.length() == 0) { #if !defined(SILENT_MODE) printDebug(imap, esp_mail_error_imap_str_11 /* "no mailbox opened" */, esp_mail_error_imap_str_11 /* "no mailbox opened" */, esp_mail_debug_tag_type_client, true, false); #endif } else { if (imap->_readOnlyMode || !imap->_mailboxOpened) { if (!imap->selectFolder(imap->_currentFolder.c_str(), false)) return false; } } #if !defined(SILENT_MODE) PGM_P p1 = NULL; PGM_P p2 = NULL; if (type == esp_mail_imap_store_flag_type_set) { p1 = esp_mail_cb_str_26; /* "Setting FLAG..." */ p2 = esp_mail_dbg_str_41; /* "setting FLAG" */ } else if (type == esp_mail_imap_store_flag_type_add) { p1 = esp_mail_cb_str_24; /* "Adding FLAG..." */ p2 = esp_mail_dbg_str_42; /* "adding FLAG" */ } else { p1 = esp_mail_cb_str_23; /* "Removing FLAG..." */ p2 = esp_mail_dbg_str_43; /* "removing FLAG" */ } printDebug(imap, p1, p2, esp_mail_debug_tag_type_client, true, false); #endif if (!sessionExisted(imap)) return false; MB_String cmd; if (UID) appendSpace(cmd, true, 2, imap_commands[esp_mail_imap_command_uid].text, imap_commands[esp_mail_imap_command_store].text); else appendSpace(cmd, true, imap_commands[esp_mail_imap_command_store].text); cmd += sequenceSet; imap->addModifier(cmd, esp_mail_imap_command_unchangedsince, modsequence); if (type == esp_mail_imap_store_flag_type_set) cmd += imap_cmd_pre_tokens[esp_mail_imap_command_flags]; else if (type == esp_mail_imap_store_flag_type_add) cmd += imap_cmd_pre_tokens[esp_mail_imap_command_plus_flags]; else cmd += imap_cmd_pre_tokens[esp_mail_imap_command_minus_flags]; if (silent) prependDot(cmd, imap_commands[esp_mail_imap_command_silent].text); appendSpace(cmd); appendString(cmd, MB_String(flag).c_str(), false, false, esp_mail_string_mark_type_round_bracket); if (imapSend(imap, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; imap->_imap_cmd = esp_mail_imap_cmd_store; if (!handleIMAPResponse(imap, IMAP_STATUS_STORE_FAILED, false)) return false; if (closeSession) imap->closeSession(); return true; } int ESP_Mail_Client::parseSearchResponse(IMAPSession *imap, esp_mail_imap_response_data &res, PGM_P tag, const char *key) { int bufLen = res.chunkBufSize; int ret = -1; char c = 0; int idx = 0; int num = 0; size_t tagLen = strlen_P(tag); MB_String _tag = tag; while (imap->client.available() > 0 && idx < bufLen) { yield_impl(); ret = imap->client.read(); if (ret > -1) { if (idx >= bufLen - 1) return idx; c = (char)ret; if (c == '\n') c = ' '; res.response[idx++] = c; if (res.chunkIdx == 0) { // Search response parsing if (strcmp(res.response, key) == 0) { res.chunkIdx++; return 0; } else { // Status response parsing res.imapResp = imapResponseStatus(imap, res.response, esp_mail_imap_tag_str); // Exit if error or complete (no messages found) if (res.imapResp != esp_mail_imap_resp_unknown) goto end_search; } } else { if (c == ' ') { imap->_mbif._searchCount++; if (imap->_imap_data->enable.recent_sort) { esp_mail_imap_msg_num_t msg_num; msg_num.type = imap->_uidSearch ? esp_mail_imap_msg_num_type_uid : esp_mail_imap_msg_num_type_number; msg_num.value = (uint32_t)atoi(res.response); imap->_imap_msg_num.push_back(msg_num); if (imap->_imap_msg_num.size() > imap->_imap_data->limit.search) imap->_imap_msg_num.erase(imap->_imap_msg_num.begin()); } else { if (imap->_imap_msg_num.size() < imap->_imap_data->limit.search) { esp_mail_imap_msg_num_t msg_num; msg_num.type = imap->_uidSearch ? esp_mail_imap_msg_num_type_uid : esp_mail_imap_msg_num_type_number; msg_num.value = (uint32_t)atoi(res.response); imap->_imap_msg_num.push_back(msg_num); } } if (imap->_debug) { num = (float)(100.0f * imap->_mbif._searchCount / imap->_mbif._msgCount); if (res.searchCount != num) { res.searchCount = num; searchReport(imap, num); } } res.chunkIdx++; return idx; } else if (idx >= (int)tagLen) { if (strpos(res.response, _tag.c_str(), 0, false) > -1) { #if defined(MB_USE_STD_VECTOR) if (imap->_imap_data->enable.recent_sort) std::sort(imap->_imap_msg_num.begin(), imap->_imap_msg_num.end(), compareMore); #else if (imap->_imap_data->enable.recent_sort) numDecSort(imap->_imap_msg_num); #endif goto end_search; } } } } } return idx; end_search: res.endSearch = true; int read = imap->client.available(); read = imap->client.readBytes(res.response + idx, read); return idx + read; } #if !defined(MB_USE_STD_VECTOR) void ESP_Mail_Client::numDecSort(_vectorImpl &arr) { struct esp_mail_imap_msg_num_t tmp; for (size_t i = 0; i < arr.size(); ++i) { for (size_t j = i + 1; j < arr.size(); ++j) { if (arr[i].value < arr[j].value) { tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } } } } #endif struct esp_mail_message_part_info_t *ESP_Mail_Client::cPart(IMAPSession *imap) { if (cHeader(imap) && imap->_cPartIdx < (int)cHeader(imap)->part_headers.size()) return &cHeader(imap)->part_headers[imap->_cPartIdx]; return nullptr; } struct esp_mail_message_header_t *ESP_Mail_Client::cHeader(IMAPSession *imap) { if (cIdx(imap) < (int)imap->_headers.size()) return &imap->_headers[cIdx(imap)]; return nullptr; } bool ESP_Mail_Client::parseHeaderField(IMAPSession *imap, const char *buf, PGM_P beginToken, bool caseSensitive, struct esp_mail_message_header_t &header, int &headerState, int state) { if (strcmpP(buf, 0, beginToken, caseSensitive)) { headerState = state; char *tmp = subStr(buf, beginToken, NULL, 0, -1, caseSensitive); if (tmp) { collectHeaderField(imap, tmp, header, headerState); // release memory freeMem(&tmp); return true; } } return false; } void ESP_Mail_Client::parseHeaderResponse(IMAPSession *imap, esp_mail_imap_response_data &res, bool caseSensitive) { char *tmp = nullptr; if (res.chunkIdx == 0) { MB_String str; joinStringDot(str, 2, imap_commands[esp_mail_imap_command_header].text, imap_commands[esp_mail_imap_command_fields].text); if (!res.isUntaggedResponse && strposP(res.response, str.c_str(), 0, caseSensitive) != -1 && res.response[0] == '*') res.isUntaggedResponse = true; if (res.isUntaggedResponse && res.response[strlen(res.response) - 1] == '}') res.untaggedRespCompleted = true; if (res.isUntaggedResponse && res.header.message_no == 0) { tmp = subStr(res.response, imap_responses[esp_mail_imap_response_untagged].text, imap_responses[esp_mail_imap_response_fetch].text, 0); if (tmp) { res.header.message_no = atoi(tmp); // release memory freeMem(&tmp); } } if (res.isUntaggedResponse && res.untaggedRespCompleted) { tmp = subStr(res.response, esp_mail_str_36 /* "{" */, esp_mail_str_37 /* "}" */, 0, 0, caseSensitive); if (tmp) { res.octetCount = 2; res.header.header_data_len = atoi(tmp); // release memory freeMem(&tmp); res.chunkIdx++; } } } else { if (res.octetCount > res.header.header_data_len + 2) return; res.chunkIdx++; MB_String field; for (int i = esp_mail_rfc822_header_field_from; i < esp_mail_rfc822_header_field_maxType; i++) { appendHeaderName(field, rfc822_headers[i].text, true, false, false); if (parseHeaderField(imap, res.response, field.c_str(), caseSensitive, res.header, res.headerState, i)) return; } appendHeaderName(field, message_headers[esp_mail_message_header_field_content_transfer_encoding].text, true, true, false); if (parseHeaderField(imap, res.response, field.c_str(), caseSensitive, res.header, res.headerState, esp_mail_imap_state_content_transfer_encoding)) return; appendHeaderName(field, message_headers[esp_mail_message_header_field_accept_language].text, true, true, false); if (parseHeaderField(imap, res.response, field.c_str(), caseSensitive, res.header, res.headerState, esp_mail_imap_state_accept_language)) return; appendHeaderName(field, message_headers[esp_mail_message_header_field_content_language].text, true, true, false); if (parseHeaderField(imap, res.response, field.c_str(), caseSensitive, res.header, res.headerState, esp_mail_imap_state_content_language)) return; MB_String contentTypeName; appendHeaderName(contentTypeName, message_headers[esp_mail_message_header_field_content_type].text, false, false, false); if (strcmpP(res.response, 0, contentTypeName.c_str(), caseSensitive)) { res.headerState = esp_mail_imap_state_content_type; tmp = subStr(res.response, contentTypeName.c_str(), esp_mail_str_35 /* ";" */, 0, 0, caseSensitive); if (tmp) { // We set attachment status here as attachment should be included in multipart/mixed message, // unless no real attachments included which we don't know until fetching the sub part. if (strpos(tmp, esp_mail_imap_multipart_sub_type_t::mixed, 0, caseSensitive) != -1) res.header.hasAttachment = true; collectHeaderField(imap, res.response, res.header, res.headerState); // release memory freeMem(&tmp); } } } } void ESP_Mail_Client::collectHeaderField(IMAPSession *imap, char *buf, struct esp_mail_message_header_t &header, int state) { size_t i = 0; while (buf[i] == ' ') { i++; if (strlen(buf) <= i) return; } if (state < esp_mail_rfc822_header_field_maxType) { int ptr = getRFC822HeaderPtr(state, &header.header_fields); if (ptr > 0) { *(addrTo(ptr)) += &buf[i]; } return; } switch (state) { case esp_mail_imap_state_content_type: header.content_type += &buf[i]; break; case esp_mail_imap_state_content_transfer_encoding: header.content_transfer_encoding += &buf[i]; break; case esp_mail_imap_state_accept_language: header.accept_language += &buf[i]; break; case esp_mail_imap_state_content_language: header.content_language += &buf[i]; break; case esp_mail_imap_state_char_set: header.char_set += &buf[i]; break; case esp_mail_imap_state_boundary: header.boundary += &buf[i]; break; default: break; } } bool ESP_Mail_Client::getDecodedHeader(IMAPSession *imap, const char *buf, PGM_P beginToken, MB_String &out, bool caseSensitive) { if (getHeader(buf, beginToken, out, caseSensitive)) { // decode header text decodeString(imap, out); return true; } return false; } void ESP_Mail_Client::checkFirmwareFile(IMAPSession *imap, const char *filename, struct esp_mail_message_part_info_t &part, bool defaultSize) { if (strcmp(filename, imap->_imap_data->firmware_update.attach_filename.c_str()) == 0 && part.attach_type == esp_mail_att_type_attachment) { part.is_firmware_file = true; // If no file size prop from Content-Disposition header if (part.attach_data_size == 0 && defaultSize) { #if defined(ESP32) || defined(ESP8266) int sketchFreeSpace = ESP.getFreeSketchSpace(); part.attach_data_size = sketchFreeSpace ? sketchFreeSpace : 1024000; #elif defined(MB_ARDUINO_PICO) size_t spiffsSize = ((size_t)&_FS_end - (size_t)&_FS_start); part.attach_data_size = spiffsSize ? spiffsSize / 2 : 1024000; #endif } if (!imap->_imap_data->firmware_update.save_to_file) part.save_to_file = false; } } void ESP_Mail_Client::parsePartHeaderResponse(IMAPSession *imap, esp_mail_imap_response_data &res, bool caseSensitive) { char *tmp = nullptr; if (res.chunkIdx == 0) { tmp = subStr(res.response, imap_responses[esp_mail_imap_response_fetch].text, NULL, 0, -1); if (tmp) { // release memory freeMem(&tmp); tmp = subStr(res.response, esp_mail_str_36 /* "{" */, esp_mail_str_37 /* "}" */, 0); if (tmp) { res.chunkIdx++; res.part.octetLen = atoi(tmp); res.octetCount = 2; // release memory freeMem(&tmp); } } } else { MB_String value, old_value; bool valueStored = false; res.chunkIdx++; // if all octets read if (res.octetCount > res.part.octetLen) { // Is inline attachment without content id or name or filename? // It is supposed to be the inline message txt content, reset attach type to none if (res.part.attach_type == esp_mail_att_type_inline && res.part.CID.length() == 0) res.part.attach_type = esp_mail_att_type_none; // Is attachment file extension missing? // append extension if (res.part.attach_type == esp_mail_att_type_inline || res.part.attach_type == esp_mail_att_type_attachment) { if (res.part.filename.length() > 0 && res.part.filename.find('.') == MB_String::npos) { MB_String ext; getExtfromMIME(res.part.content_type.c_str(), ext); res.part.filename += ext; } checkFirmwareFile(imap, res.part.filename.c_str(), res.part); } return; } // Content header field parse if (strcmpP(res.response, 0, esp_mail_str_56 /* "content-" */, caseSensitive)) { // Content-Type MB_String contentTypeName; appendHeaderName(contentTypeName, message_headers[esp_mail_message_header_field_content_type].text, false, false, false); if (strcmpP(res.response, 0, contentTypeName.c_str(), caseSensitive)) { res.part.cur_content_hdr = esp_mail_message_part_info_t::content_header_field_type; resetStringPtr(res.part); tmp = subStr(res.response, contentTypeName.c_str(), esp_mail_str_35 /* ";" */, 0, 0, caseSensitive); if (tmp) { res.part.content_type = tmp; // release memory freeMem(&tmp); int p1 = strposP(res.part.content_type.c_str(), esp_mail_imap_composite_media_type_t::multipart, 0, caseSensitive); if (p1 != -1) { p1 += strlen(esp_mail_imap_composite_media_type_t::multipart) + 1; res.part.multipart = true; // inline or embedded images if (strpos(res.part.content_type.c_str(), esp_mail_imap_multipart_sub_type_t::related, p1, caseSensitive) != -1) res.part.multipart_sub_type = esp_mail_imap_multipart_sub_type_related; // multiple text formats e.g. plain, html, enriched else if (strpos(res.part.content_type.c_str(), esp_mail_imap_multipart_sub_type_t::alternative, p1, caseSensitive) != -1) res.part.multipart_sub_type = esp_mail_imap_multipart_sub_type_alternative; // medias else if (strpos(res.part.content_type.c_str(), esp_mail_imap_multipart_sub_type_t::parallel, p1, caseSensitive) != -1) res.part.multipart_sub_type = esp_mail_imap_multipart_sub_type_parallel; // rfc822 encapsulated else if (strpos(res.part.content_type.c_str(), esp_mail_imap_multipart_sub_type_t::digest, p1, caseSensitive) != -1) res.part.multipart_sub_type = esp_mail_imap_multipart_sub_type_digest; else if (strpos(res.part.content_type.c_str(), esp_mail_imap_multipart_sub_type_t::report, p1, caseSensitive) != -1) res.part.multipart_sub_type = esp_mail_imap_multipart_sub_type_report; // others can be attachments else if (strpos(res.part.content_type.c_str(), esp_mail_imap_multipart_sub_type_t::mixed, p1, caseSensitive) != -1) res.part.multipart_sub_type = esp_mail_imap_multipart_sub_type_mixed; } p1 = strposP(res.part.content_type.c_str(), esp_mail_imap_composite_media_type_t::message, 0, caseSensitive); if (p1 != -1) { p1 += strlen(esp_mail_imap_composite_media_type_t::message) + 1; if (strpos(res.part.content_type.c_str(), esp_mail_imap_message_sub_type_t::rfc822, p1, caseSensitive) != -1) res.part.message_sub_type = esp_mail_imap_message_sub_type_rfc822; else if (strpos(res.part.content_type.c_str(), esp_mail_imap_message_sub_type_t::Partial, p1, caseSensitive) != -1) res.part.message_sub_type = esp_mail_imap_message_sub_type_partial; else if (strpos(res.part.content_type.c_str(), esp_mail_imap_message_sub_type_t::External_Body, p1, caseSensitive) != -1) res.part.message_sub_type = esp_mail_imap_message_sub_type_external_body; else if (strpos(res.part.content_type.c_str(), esp_mail_imap_message_sub_type_t::delivery_status, p1, caseSensitive) != -1) res.part.message_sub_type = esp_mail_imap_message_sub_type_delivery_status; } p1 = strpos(res.part.content_type.c_str(), esp_mail_imap_descrete_media_type_t::text, 0, caseSensitive); if (p1 != -1) { p1 += strlen(esp_mail_imap_descrete_media_type_t::text) + 1; if (strpos(res.part.content_type.c_str(), esp_mail_imap_media_text_sub_type_t::plain, p1, caseSensitive) != -1) res.part.msg_type = esp_mail_msg_type_plain; else if (strpos(res.part.content_type.c_str(), esp_mail_imap_media_text_sub_type_t::enriched, p1, caseSensitive) != -1) res.part.msg_type = esp_mail_msg_type_enriched; else if (strpos(res.part.content_type.c_str(), esp_mail_imap_media_text_sub_type_t::html, p1, caseSensitive) != -1) res.part.msg_type = esp_mail_msg_type_html; else res.part.msg_type = esp_mail_msg_type_plain; } } } // Content-Description MB_String contentDescrName; appendHeaderName(contentDescrName, message_headers[esp_mail_message_header_field_content_description].text, false, true, false); if (getDecodedHeader(imap, res.response, contentDescrName.c_str(), res.part.descr, caseSensitive)) { res.part.cur_content_hdr = esp_mail_message_part_info_t::content_header_field_description; tmp = subStr(res.response, contentDescrName.c_str(), NULL, 0, -1, caseSensitive); if (tmp) { value = tmp; // release memory freeMem(&tmp); res.part.stringPtr = toAddr(res.part.content_description); value.trim(); if (value.length() == 0) return; } } // Content-ID MB_String contentIdName; appendHeaderName(contentIdName, message_headers[esp_mail_message_header_field_content_id].text, false, true, false); if (strcmpP(res.response, 0, contentIdName.c_str(), caseSensitive)) { tmp = subStr(res.response, contentIdName.c_str(), NULL, 0, -1, caseSensitive); if (tmp) { res.part.CID = tmp; // release memory freeMem(&tmp); res.part.CID.trim(); if (res.part.CID[0] == '<') res.part.CID.erase(0, 1); if (res.part.CID[res.part.CID.length() - 1] == '>') res.part.CID.erase(res.part.CID.length() - 1, 1); // if inline attachment file name was not assigned if (res.part.attach_type == esp_mail_att_type_inline && res.part.filename.length() == 0) { // set filename from content id and append extension later res.part.filename = res.part.CID; res.part.name = res.part.filename; } } res.part.cur_content_hdr = esp_mail_message_part_info_t::content_header_field_id; resetStringPtr(res.part); } // Content-Disposition MB_String contentDispositionName; appendHeaderName(contentDispositionName, message_headers[esp_mail_message_header_field_content_disposition].text, false, true, false); if (strcmpP(res.response, 0, contentDispositionName.c_str(), caseSensitive)) { res.part.cur_content_hdr = esp_mail_message_part_info_t::content_header_field_disposition; resetStringPtr(res.part); tmp = subStr(res.response, contentDispositionName.c_str(), esp_mail_str_35 /* ";" */, 0, 0, caseSensitive); if (tmp) { // don't count altenative part text and html as embedded contents if (cHeader(imap)->multipart_sub_type != esp_mail_imap_multipart_sub_type_alternative) { res.part.content_disposition = tmp; if (caseSensitive) { if (strcmp(tmp, esp_mail_content_disposition_type_t::attachment) == 0) res.part.attach_type = esp_mail_att_type_attachment; else if (strcmp(tmp, esp_mail_content_disposition_type_t::inline_) == 0) res.part.attach_type = esp_mail_att_type_inline; } else { if (strcasecmp(tmp, esp_mail_content_disposition_type_t::attachment) == 0) res.part.attach_type = esp_mail_att_type_attachment; else if (strcasecmp(tmp, esp_mail_content_disposition_type_t::inline_) == 0) res.part.attach_type = esp_mail_att_type_inline; } } // release memory freeMem(&tmp); } } // Content-Transfer-Encoding MB_String contentTEName; appendHeaderName(contentTEName, message_headers[esp_mail_message_header_field_content_transfer_encoding].text, false, true, false); if (strcmpP(res.response, 0, contentTEName.c_str(), caseSensitive)) { // store last text field res.part.cur_content_hdr = esp_mail_message_part_info_t::content_header_field_transfer_enc; resetStringPtr(res.part); tmp = subStr(res.response, contentTEName.c_str(), NULL, 0, -1, caseSensitive); if (tmp) { res.part.content_transfer_encoding = tmp; if (strcmpP(tmp, 0, esp_mail_transfer_encoding_t::enc_base64)) res.part.xencoding = esp_mail_msg_xencoding_base64; else if (strcmpP(tmp, 0, esp_mail_transfer_encoding_t::enc_qp)) res.part.xencoding = esp_mail_msg_xencoding_qp; else if (strcmpP(tmp, 0, esp_mail_transfer_encoding_t::enc_7bit)) res.part.xencoding = esp_mail_msg_xencoding_7bit; else if (strcmpP(tmp, 0, esp_mail_transfer_encoding_t::enc_8bit)) res.part.xencoding = esp_mail_msg_xencoding_8bit; else if (strcmpP(tmp, 0, esp_mail_transfer_encoding_t::enc_binary)) res.part.xencoding = esp_mail_msg_xencoding_binary; // release memory freeMem(&tmp); } } } else { if (res.part.cur_content_hdr == esp_mail_message_part_info_t::content_header_field_none) { resetStringPtr(res.part); MB_String field; for (int i = esp_mail_rfc822_header_field_from; i < esp_mail_rfc822_header_field_maxType; i++) { field = rfc822_headers[i].text; field += esp_mail_str_34; /* ":" */ ; int ptr = getRFC822HeaderPtr(i, &res.part.rfc822_header); if (ptr > 0) { if (getDecodedHeader(imap, res.response, field.c_str(), *(addrTo(ptr)), caseSensitive)) return; } } } } // parse content type header sub type properties if (res.part.cur_content_hdr == esp_mail_message_part_info_t::content_header_field_type) { if (res.part.msg_type == esp_mail_msg_type_plain || res.part.msg_type == esp_mail_msg_type_enriched) { MB_String charset; appendLowerCaseString(charset, message_headers[esp_mail_message_header_field_charset].text, false); // We have to check for both quotes string or non quote string if (getPartHeaderProperties(imap, res.response, charset.c_str(), esp_mail_str_11 /* "\"" */, false, value, old_value, res.part.stringEnc, caseSensitive)) { res.part.charset = value; resetStringPtr(res.part); } else if (getPartHeaderProperties(imap, res.response, charset.c_str(), esp_mail_str_35 /* ";" */, true, value, old_value, res.part.stringEnc, caseSensitive)) { res.part.charset = value; resetStringPtr(res.part); } if (strposP(res.response, esp_mail_str_59 /* "format=flowed" */, 0, caseSensitive) > -1 || strposP(res.response, esp_mail_str_58 /* "format=\"flowed\"" */, 0, caseSensitive) > -1) { res.part.plain_flowed = true; resetStringPtr(res.part); } if (strposP(res.response, esp_mail_str_61 /* "delsp=yes" */, 0, caseSensitive) > -1 || strposP(res.response, esp_mail_str_60 /* "delsp=\"yes\"" */, 0, caseSensitive) > -1) { res.part.plain_delsp = true; resetStringPtr(res.part); } } if (res.part.charset.length() == 0) { MB_String charset; appendLowerCaseString(charset, message_headers[esp_mail_message_header_field_charset].text, false); // We have to check for both quotes string or non quote string if (getPartHeaderProperties(imap, res.response, charset.c_str(), esp_mail_str_11 /* "\"" */, false, value, old_value, res.part.stringEnc, caseSensitive)) { res.part.charset = value; resetStringPtr(res.part); } else if (getPartHeaderProperties(imap, res.response, charset.c_str(), esp_mail_str_35 /* ";" */, true, value, old_value, res.part.stringEnc, caseSensitive)) { res.part.charset = value; resetStringPtr(res.part); } } MB_String name; appendLowerCaseString(name, message_headers[esp_mail_message_header_field_name].text, false); // We have to check for both quotes string or non quote string if (getPartHeaderProperties(imap, res.response, name.c_str(), esp_mail_str_11 /* "\"" */, false, value, old_value, res.part.stringEnc, caseSensitive)) { res.part.stringPtr = toAddr(res.part.name); value.trim(); if (value.length() == 0) return; } else if (getPartHeaderProperties(imap, res.response, name.c_str(), esp_mail_str_35 /* ";" */, true, value, old_value, res.part.stringEnc, caseSensitive)) { res.part.stringPtr = toAddr(res.part.name); value.trim(); if (value.length() == 0) return; } } // parse content disposition header sub type properties if (res.part.cur_content_hdr == esp_mail_message_part_info_t::content_header_field_disposition && res.part.content_disposition.length() > 0) { // filename prop MB_String filename; appendLowerCaseString(filename, message_headers[esp_mail_message_header_field_filename].text, false); // We have to check for both quotes string or non quote string if (getPartHeaderProperties(imap, res.response, filename.c_str(), esp_mail_str_11 /* "\"" */, false, value, old_value, res.part.stringEnc, caseSensitive)) { res.part.stringPtr = toAddr(res.part.filename); value.trim(); if (value.length() == 0) return; } else if (getPartHeaderProperties(imap, res.response, filename.c_str(), esp_mail_str_35 /* ";" */, true, value, old_value, res.part.stringEnc, caseSensitive)) { res.part.stringPtr = toAddr(res.part.filename); value.trim(); if (value.length() == 0) return; } // size prop MB_String size; appendLowerCaseString(size, message_headers[esp_mail_message_header_field_size].text, false); if (getPartHeaderProperties(imap, res.response, size.c_str(), esp_mail_str_35 /* ";" */, true, value, old_value, res.part.stringEnc, caseSensitive)) { res.part.attach_data_size = atoi(value.c_str()); cHeader(imap)->total_attach_data_size += res.part.attach_data_size; res.part.sizeProp = true; if (!valueStored && old_value.length() > 0) valueStored = storeStringPtr(imap, res.part.stringPtr, old_value, res.response); resetStringPtr(res.part); } // creation date prop MB_String creationDate; appendLowerCaseString(creationDate, message_headers[esp_mail_message_header_field_creation_date].text, false); if (getPartHeaderProperties(imap, res.response, creationDate.c_str(), esp_mail_str_11 /* "\"" */, false, value, old_value, res.part.stringEnc, caseSensitive)) { res.part.creation_date = value; if (!valueStored && old_value.length() > 0) valueStored = storeStringPtr(imap, res.part.stringPtr, old_value, res.response); resetStringPtr(res.part); } // mod date prop MB_String modDate; appendLowerCaseString(modDate, message_headers[esp_mail_message_header_field_modification_date].text, false); if (getPartHeaderProperties(imap, res.response, modDate.c_str(), esp_mail_str_11 /* "\"" */, false, value, old_value, res.part.stringEnc, caseSensitive)) { res.part.modification_date = value; if (!valueStored && old_value.length() > 0) valueStored = storeStringPtr(imap, res.part.stringPtr, old_value, res.response); resetStringPtr(res.part); } } if (!valueStored && (res.part.cur_content_hdr == esp_mail_message_part_info_t::content_header_field_description || res.part.cur_content_hdr == esp_mail_message_part_info_t::content_header_field_type || res.part.cur_content_hdr == esp_mail_message_part_info_t::content_header_field_disposition)) storeStringPtr(imap, res.part.stringPtr, value, res.response); } } void ESP_Mail_Client::resetStringPtr(struct esp_mail_message_part_info_t &part) { part.stringPtr = 0; part.stringEnc = esp_mail_char_decoding_scheme_default; } int ESP_Mail_Client::countChar(const char *buf, char find) { if (!buf) return 0; int count = 0; for (size_t i = 0; i < strlen(buf); i++) { if (buf[i] == find) count++; } return count; } bool ESP_Mail_Client::storeStringPtr(IMAPSession *imap, uint32_t addr, MB_String &value, const char *buf) { if (addr) { MB_String *a = addrTo(addr); MB_String s = value.length() > 0 ? value : buf; // is value string contains double quotes? // trim it if (countChar(s.c_str(), '"') == 2) s.trim(); if (s[0] == '"') s.erase(0, 1); if (s[s.length() - 1] == '"') s.erase(s.length() - 1, 1); decodeString(imap, s); *a += s; return true; } return false; } bool ESP_Mail_Client::getPartHeaderProperties(IMAPSession *imap, const char *buf, PGM_P p, PGM_P e, bool num, MB_String &value, MB_String &old_value, esp_mail_char_decoding_scheme &scheme, bool caseSensitive) { MB_String str = p; str += esp_mail_str_7; /* "=" */ if (!num) str += esp_mail_str_11; /* "\"" */ char *tmp = subStr(buf, str.c_str(), e, 0, 0, caseSensitive); if (!tmp) { str = p; str += esp_mail_str_7; /* "=" */ tmp = subStr(buf, str.c_str(), e, 0, 0, caseSensitive); if (tmp) { // other sub headers found? int p2 = strposP(tmp, esp_mail_str_35 /* ";" */, 0, caseSensitive); if (p2 > -1) { // release memory freeMem(&tmp); tmp = subStr(buf, str.c_str(), esp_mail_str_35 /* ";" */, 0, 0, caseSensitive); } // release memory in case above condition does not match freeMem(&tmp); } else { // Extended notation rfc5987 str = p; str += esp_mail_str_3; /* "*" */ int p2 = strpos(buf, str.c_str(), 0, caseSensitive); if (p2 > -1) { int p3 = strposP(buf, esp_mail_str_3 /* "*" */, p2 + str.length() + 1, caseSensitive); if (p3 > -1 && p3 < (int)strlen(buf)) { MB_String charset; p3 += 2; int p4 = strpos(buf, "'", p3, caseSensitive); if (p4 > -1) { scheme = getEncodingFromCharset(buf); int c1 = p4 + 1; p4 = strpos(buf, "'", p4 + 1, caseSensitive); int c2 = p4; if (c2 > -1) { charset.append(buf + c1, c2 - c1); } p3 = p4 + 1; } int len = strlen(buf) - p3; tmp = allocMem(len + 1); if (buf[strlen(buf) - 1] == ';') len--; memcpy(tmp, &buf[p3], len); if (scheme == esp_mail_char_decoding_scheme_utf_8) { char *buf2 = urlDecode(tmp); // release memory and point to new buffer freeMem(&tmp); tmp = buf2; } else if (imap->_charDecCallback) { IMAP_Decoding_Info decoding; decoding.charset = charset.c_str(); decoding.data = tmp; decoding.type = IMAP_Decoding_Info::message_part_type_header; imap->_charDecCallback(&decoding); if (decoding.decodedString.length() > 0) { char *buf2 = allocMem(decoding.decodedString.length() + 1); strcpy(buf2, decoding.decodedString.c_str()); // release memory and point to new buffer freeMem(&tmp); tmp = buf2; } } else if (scheme == esp_mail_char_decoding_scheme_iso8859_1) { int ilen = strlen(tmp); int olen = (ilen + 1) * 2; char *buf2 = allocMem(olen); decodeLatin1_UTF8((unsigned char *)buf2, &olen, (unsigned char *)tmp, &ilen); // release memory and point to new buffer freeMem(&tmp); tmp = buf2; } else if (scheme == esp_mail_char_decoding_scheme_tis_620 || scheme == esp_mail_char_decoding_scheme_iso8859_11 || scheme == esp_mail_char_decoding_scheme_windows_874) { int ilen = strlen(tmp); char *buf2 = allocMem((ilen + 1) * 3); decodeTIS620_UTF8(buf2, tmp, ilen); // release memory and point to new buffer freeMem(&tmp); tmp = buf2; } } } } } if (tmp) { old_value = value; value = tmp; // release memory freeMem(&tmp); return true; } return false; } char *ESP_Mail_Client::urlDecode(const char *str) { int d = 0; /* whether or not the string is decoded */ char *dStr = allocMem(strlen(str) + 1); char eStr[] = "00"; /* for a hex code */ strcpy(dStr, str); while (!d) { d = 1; size_t i; /* the counter for the string */ for (i = 0; i < strlen(dStr); ++i) { if (dStr[i] == '%') { if (dStr[i + 1] == 0) return dStr; if (isxdigit(dStr[i + 1]) && isxdigit(dStr[i + 2])) { d = 0; /* combine the next to numbers into one */ eStr[0] = dStr[i + 1]; eStr[1] = dStr[i + 2]; /* convert it to decimal */ long int x = strtol(eStr, NULL, 16); /* remove the hex */ memmove(&dStr[i + 1], &dStr[i + 3], strlen(&dStr[i + 3]) + 1); dStr[i] = x; } } } } return dStr; } bool ESP_Mail_Client::handleIMAPResponse(IMAPSession *imap, int errCode, bool closeSession) { if (!reconnect(imap)) return false; esp_mail_imap_response_data res(imap->client.available()); imap->_lastProgress = -1; // Flag used for CRLF inclusion in response reading in case 8bit/binary attachment and base64 encoded and binary messages bool withLineBreak = imap->_imap_cmd == esp_mail_imap_cmd_fetch_body_text && (cPart(imap)->xencoding == esp_mail_msg_xencoding_base64 || cPart(imap)->xencoding == esp_mail_msg_xencoding_binary); withLineBreak |= imap->_imap_cmd == esp_mail_imap_cmd_fetch_body_attachment && cPart(imap)->xencoding != esp_mail_msg_xencoding_base64; // custom cmd IDLE?, waiting incoming server response if (res.chunkBufSize == 0 && imap->_prev_imap_custom_cmd == imap->_imap_custom_cmd && imap->_imap_custom_cmd == esp_mail_imap_cmd_idle) { if (!reconnect(imap)) return false; return true; } while (imap->connected() && res.chunkBufSize <= 0) { if (!reconnect(imap, res.dataTime)) return false; if (!connected(imap)) { #if defined(ESP32) if (imap->_imap_cmd == esp_mail_imap_cmd_logout) // suppress the error due to server closes the connection immediately in ESP32 core v2.0.4 return true; #endif if (millis() - imap->_last_network_error_ms > 1000) { imap->_last_network_error_ms = millis(); errorStatusCB(imap, nullptr, MAIL_CLIENT_ERROR_CONNECTION_CLOSED, true); } return false; } res.chunkBufSize = imap->client.available(); yield_impl(); } res.dataTime = millis(); if (res.chunkBufSize > 0) { if (imap->_imap_cmd == esp_mail_imap_cmd_examine) { imap->_mbif.clear(); imap->_nextUID.clear(); imap->_unseenMsgIndex.clear(); } if (imap->_imap_cmd == esp_mail_imap_cmd_search) { imap->_mbif._searchCount = 0; imap->_imap_msg_num.clear(); } // response buffer res.chunkBufSize = ESP_MAIL_CLIENT_RESPONSE_BUFFER_SIZE; res.response = allocMem(res.chunkBufSize + 1); if (imap->_imap_cmd == esp_mail_imap_cmd_fetch_body_attachment || imap->_imap_cmd == esp_mail_imap_cmd_fetch_body_inline) res.lastBuf = allocMem(BASE64_CHUNKED_LEN + 1); while (!res.completedResponse) // looking for operation finishing { yield_impl(); if (imap->_imap_cmd == esp_mail_imap_cmd_append || (imap->_imap_custom_cmd == esp_mail_imap_cmd_append && imap->_imap_cmd == esp_mail_imap_cmd_custom && imap->_customCmdResCallback)) { // No waiting time out for APPEND res.dataTime = millis(); } if (!reconnect(imap, res.dataTime) || !connected(imap)) { if (!connected(imap)) { if (cPart(imap) && cPart(imap)->file_open_write) mbfs->close(mbfs_type imap->_imap_data->storage.type); #if defined(ESP32) if (imap->_imap_cmd == esp_mail_imap_cmd_logout) // suppress the error due to server closes the connection immediately in ESP32 core v2.0.4 return true; #endif if (millis() - imap->_last_network_error_ms > 1000) { imap->_last_network_error_ms = millis(); errorStatusCB(imap, nullptr, MAIL_CLIENT_ERROR_CONNECTION_CLOSED, true); } return false; } return false; } res.chunkBufSize = imap->client.available(); if (res.chunkBufSize > 0) { res.chunkBufSize = ESP_MAIL_CLIENT_RESPONSE_BUFFER_SIZE; if (imap->_imap_cmd == esp_mail_imap_cmd_search) { res.readLen = parseSearchResponse(imap, res, esp_mail_imap_tag_str, imap_responses[esp_mail_imap_response_search].text); imap->_mbif._availableItems = imap->_imap_msg_num.size(); if (imap->_mbif._availableItems == 0) { res.imapResp = imapResponseStatus(imap, res.response, esp_mail_imap_tag_str); if (res.imapResp != esp_mail_imap_resp_unknown) res.endSearch = true; if (res.imapResp == esp_mail_imap_resp_bad) { errorStatusCB(imap, nullptr, IMAP_STATUS_BAD_COMMAND, false); return false; } } } else { // response read as chunk ended with CRLF or complete buffer size int o = res.octetCount; res.readLen = 0; MB_String ovfBuf; if (!readResponse(imap, res.response, res.chunkBufSize, res.readLen, withLineBreak, res.octetCount, ovfBuf)) { errorStatusCB(imap, nullptr, MAIL_CLIENT_ERROR_READ_TIMEOUT, true); return false; } // If buffer overflown, copy from overflow buffer if (ovfBuf.length() > 0) { // release memory freeMem(&res.response); res.response = allocMem(ovfBuf.length() + 1); strcpy(res.response, ovfBuf.c_str()); ovfBuf.clear(); } if (res.readLen == 0 && o != res.octetCount && res.octetCount <= res.octetLength && cPart(imap)->xencoding != esp_mail_msg_xencoding_base64 && cPart(imap)->xencoding != esp_mail_msg_xencoding_binary && cPart(imap)->xencoding != esp_mail_msg_xencoding_qp) { strcpy_P(res.response, esp_mail_str_42 /* "\r\n" */); res.readLen = 2; } } if (res.readLen) { if (imap->_debug && imap->_debugLevel > esp_mail_debug_level_basic && !imap->_customCmdResCallback) { if (imap->_imap_cmd != esp_mail_imap_cmd_search && imap->_imap_cmd != esp_mail_imap_cmd_fetch_body_text && imap->_imap_cmd != esp_mail_imap_cmd_fetch_body_attachment && imap->_imap_cmd != esp_mail_imap_cmd_fetch_body_inline) esp_mail_debug_print((const char *)res.response, true); } if (imap->_imap_cmd != esp_mail_imap_cmd_search || (imap->_imap_cmd == esp_mail_imap_cmd_search && res.endSearch)) res.imapResp = imapResponseStatus(imap, res.response, esp_mail_imap_tag_str); if (res.imapResp != esp_mail_imap_resp_unknown) { // We've got the right response, // prepare to exit res.completedResponse = true; if (imap->_debug && imap->_debugLevel > esp_mail_debug_level_basic && !imap->_customCmdResCallback) { if (imap->_imap_cmd == esp_mail_imap_cmd_fetch_body_text || imap->_imap_cmd == esp_mail_imap_cmd_fetch_body_attachment || imap->_imap_cmd == esp_mail_imap_cmd_fetch_body_inline) esp_mail_debug_print((const char *)res.response, true); } MB_String ovfBuf; while (imap->client.available()) { if (!readResponse(imap, res.response, res.chunkBufSize, res.readLen, true, res.octetCount, ovfBuf)) { errorStatusCB(imap, nullptr, MAIL_CLIENT_ERROR_READ_TIMEOUT, true); return false; } // If buffer overflown, copy from overflow buffer if (ovfBuf.length() > 0) { // release memory freeMem(&res.response); res.response = allocMem(ovfBuf.length() + 1); strcpy(res.response, ovfBuf.c_str()); ovfBuf.clear(); } if (res.readLen) { if (imap->_debug && imap->_debugLevel > esp_mail_debug_level_basic && !imap->_customCmdResCallback) { if (imap->_imap_cmd == esp_mail_imap_cmd_fetch_body_text || imap->_imap_cmd == esp_mail_imap_cmd_fetch_body_attachment || imap->_imap_cmd == esp_mail_imap_cmd_fetch_body_inline) esp_mail_debug_print((const char *)res.response, true); } } } } else { // No response ever parsed if (imap->_imap_cmd == esp_mail_imap_cmd_sasl_auth_plain || imap->_imap_cmd == esp_mail_imap_cmd_sasl_auth_oauth) { if (imap->_imap_cmd == esp_mail_imap_cmd_sasl_auth_oauth) { if (isOAuthError(res.response, res.readLen, res.chunkIdx, 2)) res.completedResponse = true; } // In case SASL-IR extension does not support, check for initial zero-length server challenge first "+ " if (!imap->_auth_capability[esp_mail_auth_capability_sasl_ir] && strcmp(res.response, pgm2Str(esp_mail_str_63 /* "+ " */)) == 0) { res.imapResp = esp_mail_imap_resp_ok; res.completedResponse = true; } } if (imap->_imap_cmd == esp_mail_imap_cmd_sasl_login || imap->_imap_cmd == esp_mail_imap_cmd_sasl_auth_oauth || imap->_imap_cmd == esp_mail_imap_cmd_sasl_auth_plain) { int i = 0; if (parseCapabilityResponse(imap, res.response, i)) imap->_feature_capability[esp_mail_imap_read_capability_auto_caps] = true; } else if (imap->_imap_cmd == esp_mail_imap_cmd_custom && imap->_customCmdResCallback) { res.imapResp = imapResponseStatus(imap, res.response, imap->_responseStatus.tag.c_str()); // get response or custom cmd APPEND or custom cmd IDLE? if (res.imapResp > esp_mail_imap_resp_unknown || strposP(imap->_cmd.c_str(), imap_commands[esp_mail_imap_command_append].text, 0, false) > -1 || imap->_imap_custom_cmd == esp_mail_imap_cmd_idle) res.completedResponse = true; imap->_responseStatus.text = res.response; imap->_customCmdResCallback(imap->_responseStatus); if (res.completedResponse) return true; } else if (imap->_imap_cmd == esp_mail_imap_cmd_append) { res.imapResp = esp_mail_imap_resp_ok; res.completedResponse = true; } else if (imap->_imap_cmd == esp_mail_imap_cmd_capability) parseCapabilityResponse(imap, res.response, res.chunkIdx); else if (imap->_imap_cmd == esp_mail_imap_cmd_list) parseFoldersResponse(imap, res.response, true); else if (imap->_imap_cmd == esp_mail_imap_cmd_lsub) parseFoldersResponse(imap, res.response, false); else if (imap->_imap_cmd == esp_mail_imap_cmd_select || imap->_imap_cmd == esp_mail_imap_cmd_examine) parseExamineResponse(imap, res.response); else if (imap->_imap_cmd == esp_mail_imap_cmd_get_uid) { MB_String str; appendFetchString(str, true); parseCmdResponse(imap, res.response, str.c_str()); } else if (imap->_imap_cmd == esp_mail_imap_cmd_get_flags) { MB_String str; appendFetchString(str, false); parseCmdResponse(imap, res.response, str.c_str()); } else if (imap->_imap_cmd == esp_mail_imap_cmd_get_quota) parseCmdResponse(imap, res.response, imap_responses[esp_mail_imap_response_quota].text); else if (imap->_imap_cmd == esp_mail_imap_cmd_id) parseCmdResponse(imap, res.response, imap_responses[esp_mail_imap_response_id].text); else if (imap->_imap_cmd == esp_mail_imap_cmd_get_quota_root) { parseCmdResponse(imap, res.response, imap_responses[esp_mail_imap_response_quotaroot].text); imap->_quota_tmp.clear(); parseCmdResponse(imap, res.response, imap_responses[esp_mail_imap_response_quota].text); if (imap->_quota_tmp.length() > 0) { imap->_quota_root_tmp += (char)':'; imap->_quota_root_tmp += imap->_quota_tmp; } } else if (imap->_imap_cmd == esp_mail_imap_cmd_get_acl || imap->_imap_cmd == esp_mail_imap_cmd_my_rights) parseCmdResponse(imap, res.response, imap->_imap_cmd == esp_mail_imap_cmd_get_acl ? imap_responses[esp_mail_imap_response_acl].text : imap_responses[esp_mail_imap_response_myrights].text); else if (imap->_imap_cmd == esp_mail_imap_cmd_namespace) parseCmdResponse(imap, res.response, imap_responses[esp_mail_imap_response_namespace].text); else if (imap->_imap_cmd == esp_mail_imap_cmd_idle) { res.completedResponse = res.response[0] == '+'; res.imapResp = esp_mail_imap_resp_ok; imap->_last_host_check_ms = millis(); } else if (imap->_imap_cmd == esp_mail_imap_cmd_fetch_sequence_set) { MB_String str; appendFetchString(str, true); parseCmdResponse(imap, res.response, str.c_str()); } else if (imap->_imap_cmd == esp_mail_imap_cmd_fetch_body_header) { if (res.headerState == 0 && cMSG(imap).type == esp_mail_imap_msg_num_type_uid) res.header.message_uid = cMSG(imap).value; int _st = res.headerState; parseHeaderResponse(imap, res, imap->_imap_data->enable.header_case_sensitive); if (_st == res.headerState && res.headerState > 0 && res.octetCount <= res.header.header_data_len) collectHeaderField(imap, res.response, res.header, res.headerState); } else if (imap->_imap_cmd == esp_mail_imap_cmd_fetch_body_mime) parsePartHeaderResponse(imap, res, imap->_imap_data->enable.header_case_sensitive); else if (imap->_imap_cmd == esp_mail_imap_cmd_fetch_body_text) decodeText(imap, res); else if (imap->_imap_cmd == esp_mail_imap_cmd_fetch_body_attachment || imap->_imap_cmd == esp_mail_imap_cmd_fetch_body_inline) { if (cPart(imap)->xencoding == esp_mail_msg_xencoding_base64) { // Multi-line chunked base64 string attachment handle if (res.octetCount < res.octetLength && res.readLen < BASE64_CHUNKED_LEN) { if (strlen(res.lastBuf) > 0) { res.buf = allocMem(res.readLen + strlen(res.lastBuf) + 2); strcpy(res.buf, res.lastBuf); strcat(res.buf, res.response); res.readLen = strlen(res.buf); res.tmo = parseAttachmentResponse(imap, res.buf, res); // release memory freeMem(&res.buf); memset(res.lastBuf, 0, BASE64_CHUNKED_LEN + 1); if (!res.tmo) break; } else if (res.readLen < BASE64_CHUNKED_LEN + 1) strcpy(res.lastBuf, res.response); } else { res.tmo = parseAttachmentResponse(imap, res.response, res); if (!res.tmo) break; } } else res.tmo = parseAttachmentResponse(imap, res.response, res); } res.dataTime = millis(); } } memset(res.response, 0, res.chunkBufSize); } } if (imap->_imap_cmd == esp_mail_imap_cmd_search) { if (imap->_debug && res.searchCount > 0 && res.searchCount < 100) { searchReport(imap, 100); } } } if ((res.imapResp != esp_mail_imap_resp_ok && imap->_imap_cmd == esp_mail_imap_cmd_fetch_body_header && res.header.header_data_len == 0) || res.imapResp == esp_mail_imap_resp_no) { // We don't get any response if (res.imapResp == esp_mail_imap_resp_no) imap->_responseStatus.errorCode = IMAP_STATUS_IMAP_RESPONSE_FAILED; else if (imap->_imap_data->fetch.modsequence > -1 && imap->isModseqSupported() && res.imapResp == esp_mail_imap_resp_ok && res.header.header_data_len == 0) imap->_responseStatus.errorCode = IMAP_STATUS_CHANGEDSINC_MODSEQ_TEST_FAILED; else imap->_responseStatus.errorCode = IMAP_STATUS_NO_MESSAGE; #if !defined(SILENT_MODE) if (imap->_statusCallback && imap->_imap_cmd != esp_mail_imap_cmd_fetch_body_mime) sendErrorCB(imap, imap->errorReason().c_str(), false, false); if (imap->_debug && imap->_imap_cmd != esp_mail_imap_cmd_fetch_body_mime) esp_mail_debug_print_tag(imap->errorReason().c_str(), esp_mail_debug_tag_type_error, true); #endif return false; } // We've got OK or NO responses if (res.imapResp == esp_mail_imap_resp_ok) { // Response OK if (imap->_imap_cmd == esp_mail_imap_cmd_fetch_body_header) { // Headers management char *buf = allocMem(res.header.content_type.length() + 1); strcpy(buf, res.header.content_type.c_str()); res.header.content_type.clear(); MB_String contentTypeName; appendHeaderName(contentTypeName, message_headers[esp_mail_message_header_field_content_type].text, false, false, false); res.buf = subStr(buf, contentTypeName.c_str(), esp_mail_str_35 /* ";" */, 0, 0, false); if (res.buf) { res.headerState = esp_mail_imap_state_content_type; collectHeaderField(imap, res.buf, res.header, res.headerState); // release memory freeMem(&res.buf); if (res.header.content_type.length() > 0) { int p1 = strposP(res.header.content_type.c_str(), esp_mail_imap_composite_media_type_t::multipart, 0); if (p1 != -1) { p1 += strlen(esp_mail_imap_composite_media_type_t::multipart) + 1; res.header.multipart = true; // inline or embedded images if (strpos(res.header.content_type.c_str(), esp_mail_imap_multipart_sub_type_t::related, p1) != -1) res.header.multipart_sub_type = esp_mail_imap_multipart_sub_type_related; // multiple text formats e.g. plain, html, enriched else if (strpos(res.header.content_type.c_str(), esp_mail_imap_multipart_sub_type_t::alternative, p1) != -1) res.header.multipart_sub_type = esp_mail_imap_multipart_sub_type_alternative; // medias else if (strpos(res.header.content_type.c_str(), esp_mail_imap_multipart_sub_type_t::parallel, p1) != -1) res.header.multipart_sub_type = esp_mail_imap_multipart_sub_type_parallel; // rfc822 encapsulated else if (strpos(res.header.content_type.c_str(), esp_mail_imap_multipart_sub_type_t::digest, p1) != -1) res.header.multipart_sub_type = esp_mail_imap_multipart_sub_type_digest; else if (strpos(res.header.content_type.c_str(), esp_mail_imap_multipart_sub_type_t::report, p1) != -1) res.header.multipart_sub_type = esp_mail_imap_multipart_sub_type_report; // others can be attachments else if (strpos(res.header.content_type.c_str(), esp_mail_imap_multipart_sub_type_t::mixed, p1) != -1) res.header.multipart_sub_type = esp_mail_imap_multipart_sub_type_mixed; } p1 = strposP(res.header.content_type.c_str(), esp_mail_imap_composite_media_type_t::message, 0); if (p1 != -1) { p1 += strlen(esp_mail_imap_composite_media_type_t::message) + 1; if (strpos(res.part.content_type.c_str(), esp_mail_imap_message_sub_type_t::rfc822, p1) != -1) { res.header.rfc822_part = true; res.header.message_sub_type = esp_mail_imap_message_sub_type_rfc822; } else if (strpos(res.part.content_type.c_str(), esp_mail_imap_message_sub_type_t::Partial, p1) != -1) res.header.message_sub_type = esp_mail_imap_message_sub_type_partial; else if (strpos(res.part.content_type.c_str(), esp_mail_imap_message_sub_type_t::External_Body, p1) != -1) res.header.message_sub_type = esp_mail_imap_message_sub_type_external_body; else if (strpos(res.part.content_type.c_str(), esp_mail_imap_message_sub_type_t::delivery_status, p1) != -1) res.header.message_sub_type = esp_mail_imap_message_sub_type_delivery_status; } } MB_String charset; appendLowerCaseString(charset, message_headers[esp_mail_message_header_field_charset].text, false); charset += esp_mail_str_7; /* "=" */ res.buf = subStr(buf, charset.c_str(), NULL, 0, -1, false); if (res.buf) { res.headerState = esp_mail_imap_state_char_set; collectHeaderField(imap, res.buf, res.header, res.headerState); // release memory freeMem(&res.buf); } if (res.header.multipart) { if (strcmpP(buf, 0, esp_mail_str_64 /* "boundary=\"" */)) { res.buf = subStr(buf, esp_mail_str_64 /* "boundary=\"" */, esp_mail_str_11 /* "\"" */, 0, 0, false); if (res.buf) { res.headerState = esp_mail_imap_state_boundary; collectHeaderField(imap, res.buf, res.header, res.headerState); // release memory freeMem(&res.buf); } } } } // release memory freeMem(&buf); // Decode the headers fields for (int i = esp_mail_rfc822_header_field_from; i < esp_mail_rfc822_header_field_maxType; i++) { if (i != esp_mail_rfc822_header_field_msg_id && i != esp_mail_rfc822_header_field_flags) decodeString(imap, res.header.header_fields.header_items[i]); } imap->_headers.push_back(res.header); } if (imap->_imap_cmd == esp_mail_imap_cmd_fetch_body_mime) { // Expect the octet length in the response for the existent part if (res.part.octetLen > 0) { res.part.partNumStr = cHeader(imap)->partNumStr; res.part.partNumFetchStr = cHeader(imap)->partNumStr; if (cHeader(imap)->part_headers.size() > 0) { struct esp_mail_message_part_info_t *_part = &cHeader(imap)->part_headers[cHeader(imap)->part_headers.size() - 1]; bool rfc822_body_subtype = _part->message_sub_type == esp_mail_imap_message_sub_type_rfc822; if (rfc822_body_subtype) { if (!_part->rfc822_part) { // additional rfc822 message header, store it to the rfc822 part header _part->rfc822_part = true; _part->rfc822_header = res.part.rfc822_header; imap->_rfc822_part_count++; _part->rfc822_msg_Idx = imap->_rfc822_part_count; } } } cHeader(imap)->part_headers.push_back(res.part); cHeader(imap)->message_data_count = cHeader(imap)->part_headers.size(); if (res.part.msg_type != esp_mail_msg_type_none || res.part.attach_type != esp_mail_att_type_none) { if (res.part.attach_type == esp_mail_att_type_attachment || res.part.message_sub_type != esp_mail_imap_message_sub_type_rfc822) { if (res.part.attach_type != esp_mail_att_type_none && cHeader(imap)->multipart_sub_type != esp_mail_imap_multipart_sub_type_alternative) { cHeader(imap)->hasAttachment = true; cHeader(imap)->attachment_count++; } } } } else { // nonexistent part // return false to exit the loop without closing the connection if (closeSession) imap->closeSession(); return false; } } if (imap->_imap_cmd == esp_mail_imap_cmd_fetch_body_attachment || imap->_imap_cmd == esp_mail_imap_cmd_fetch_body_text || imap->_imap_cmd == esp_mail_imap_cmd_fetch_body_inline) { if (cPart(imap) && cPart(imap)->file_open_write) mbfs->close(mbfs_type imap->_imap_data->storage.type); } if (cPart(imap) && imap->_imap_cmd == esp_mail_imap_cmd_fetch_body_text) cPart(imap)->text[cPart(imap)->textLen] = 0; } else { // Response NO // Some server responses NO and should exit (false) from MIME feching loop without // closing the session if (imap->_imap_cmd != esp_mail_imap_cmd_fetch_body_mime) return handleIMAPError(imap, errCode, false); if (closeSession) imap->closeSession(); return false; } return true; } void ESP_Mail_Client::addHeader(MB_String &s, PGM_P name, const char *s_value, int num_value, bool trim, bool isJson) { if (isJson) { s += (s.length() > 0) ? esp_mail_str_77 /* ",\"" */ : esp_mail_str_78 /* "{\"" */; s += name; if (strlen(s_value) > 0) { s += esp_mail_str_79; /* "\":\"" */ ; if (trim) { MB_String t = s_value; t.replaceAll("\"", ""); s += t; } else s += s_value; s += esp_mail_str_11; /* "\"" */ } else { s += esp_mail_str_34 /* ":" */; appendSpace(s); s += num_value; } } else { if (s.length() > 0) appendNewline(s); s += name; s += esp_mail_str_34 /* ":" */; appendSpace(s); if (strlen(s_value) > 0) s += s_value; else s += num_value; } } void ESP_Mail_Client::saveHeader(IMAPSession *imap, bool json) { if (!imap->_storageReady) return; MB_String headerFilePath; prepareFilePath(imap, headerFilePath, true); headerFilePath += json ? esp_mail_str_65 /* "/header.json" */ : esp_mail_str_66 /* "/header.txt" */; prepareFileList(imap, headerFilePath); int sz = mbfs->open(headerFilePath, mbfs_type imap->_imap_data->storage.type, mb_fs_open_mode_write); if (sz < 0) { imap->_responseStatus.errorCode = sz; imap->_responseStatus.text.clear(); #if !defined(SILENT_MODE) if (imap->_debug) { esp_mail_debug_print_tag(imap->errorReason().c_str(), esp_mail_debug_tag_type_error, true); } #endif return; } MB_String s; for (size_t i = 0; i < imap->_headers.size(); i++) addHeaderItem(s, &imap->_headers[i], json); if (json) s += esp_mail_str_75; /* "]}" */ mbfs->print(mbfs_type imap->_imap_data->storage.type, s.c_str()); mbfs->close(mbfs_type imap->_imap_data->storage.type); imap->_headerSaved = true; } void ESP_Mail_Client::addHeaderItem(MB_String &str, esp_mail_message_header_t *header, bool json) { MB_String s; if (json) { if (str.length() > 0) str += esp_mail_str_8; /* "," */ else str = esp_mail_str_76; /* "{\"Messages\":[" */ } addHeader(s, message_headers[esp_mail_message_header_field_number].text, "", header->message_no, false, json); addHeader(s, message_headers[esp_mail_message_header_field_uid].text, "", header->message_uid, false, json); if (header->accept_language.length() > 0) addHeader(s, message_headers[esp_mail_message_header_field_accept_language].text, header->accept_language.c_str(), 0, false, json); if (header->content_language.length() > 0) addHeader(s, message_headers[esp_mail_message_header_field_content_language].text, header->content_language.c_str(), 0, false, json); addRFC822Headers(s, &header->header_fields, json); for (size_t j = 0; j < header->part_headers.size(); j++) { if (header->part_headers[j].rfc822_part) { MB_String s1; addRFC822Headers(s1, &header->part_headers[j].rfc822_header, json); if (json) { s += esp_mail_str_69; /* ",\"RFC822\":" */ s += s1; s += esp_mail_str_36; /* "}" */ } else { s += esp_mail_str_70; /* "\r\n\r\nRFC822:\r\n" */ s += s1; } } } if (header->attachment_count > 0) { if (json) { s += esp_mail_str_71 /* ",\"Attachments\":{\"Count\":" */; s += header->attachment_count; s += esp_mail_str_72; /* ",\"Files\":[" */ } else { s += esp_mail_str_73; /* "\r\n\r\nAttachments (" */ s += header->attachment_count; s += esp_mail_str_74; /* ")\r\n" */ } int index = 0; for (size_t j = 0; j < header->part_headers.size(); j++) { if (header->part_headers[j].attach_type == esp_mail_att_type_none || header->part_headers[j].rfc822_part) continue; if (json) { if (index > 0) s += esp_mail_str_8; /* "," */ s += esp_mail_str_67; /* /"{\"Filename\":\"" */ s += header->part_headers[j].filename; s += esp_mail_str_11; /* "\"" */ } else { if (index > 0) appendNewline(s); appendNewline(s); s += esp_mail_str_68; /* /"Index: " */ s += index + 1; addHeader(s, message_headers[esp_mail_message_header_field_filename].text, header->part_headers[j].filename.c_str(), 0, false, json); } addHeader(s, message_headers[esp_mail_message_header_field_name].text, header->part_headers[j].name.c_str(), 0, false, json); addHeader(s, message_headers[esp_mail_message_header_field_size].text, "", header->part_headers[j].attach_data_size, false, json); addHeader(s, message_headers[esp_mail_message_header_field_mime].text, header->part_headers[j].content_type.c_str(), 0, false, json); addHeader(s, message_headers[esp_mail_message_header_field_type].text, header->part_headers[j].attach_type == esp_mail_att_type_attachment ? esp_mail_content_disposition_type_t::attachment : esp_mail_content_disposition_type_t::inline_, 0, false, json); addHeader(s, message_headers[esp_mail_message_header_field_description].text, header->part_headers[j].content_description.c_str(), 0, false, json); addHeader(s, message_headers[esp_mail_message_header_field_creation_date].text, header->part_headers[j].creation_date.c_str(), 0, false, json); if (json) s += esp_mail_str_36; /* "}" */ index++; } if (json) s += esp_mail_str_75; /* "]}" */ } if (json) { s += esp_mail_str_36; /* "}" */ } str += s; } int ESP_Mail_Client::getRFC822HeaderPtr(int index, esp_mail_imap_rfc822_msg_header_item_t *header) { if (index > esp_mail_rfc822_header_field_from && index < esp_mail_rfc822_header_field_maxType) return toAddr(header->header_items[index]); return 0; } void ESP_Mail_Client::addRFC822Headers(MB_String &s, esp_mail_imap_rfc822_msg_header_item_t *header, bool json) { for (int i = esp_mail_rfc822_header_field_from; i < esp_mail_rfc822_header_field_maxType; i++) addRFC822HeaderItem(s, header, i, json); } void ESP_Mail_Client::addRFC822HeaderItem(MB_String &s, esp_mail_imap_rfc822_msg_header_item_t *header, int index, bool json) { int ptr = getRFC822HeaderPtr(index, header); if (ptr > 0) addHeader(s, rfc822_headers[index].text, addrTo(ptr)->c_str(), 0, rfc822_headers[index].trim, json); } esp_mail_imap_response_status ESP_Mail_Client::imapResponseStatus(IMAPSession *imap, char *response, PGM_P tag) { imap->_responseStatus.clear(false); MB_String test; esp_mail_imap_response_status status = esp_mail_imap_resp_unknown; esp_mail_imap_response_types type = esp_mail_imap_response_maxType; if (strpos(response, imap->prependTag(imap_responses[esp_mail_imap_response_ok].text, tag).c_str(), 0) > -1) { status = esp_mail_imap_resp_ok; type = esp_mail_imap_response_ok; } else if (strpos(response, imap->prependTag(imap_responses[esp_mail_imap_response_no].text, tag).c_str(), 0) > -1) { status = esp_mail_imap_resp_no; type = esp_mail_imap_response_no; } else if (strpos(response, imap->prependTag(imap_responses[esp_mail_imap_response_bad].text, tag).c_str(), 0) > -1) { status = esp_mail_imap_resp_bad; type = esp_mail_imap_response_bad; } if (status != esp_mail_imap_resp_unknown) { test = imap->prependTag(imap_responses[type].text, tag); imap->_responseStatus.text = &response[test.length()]; if (imap->_responseStatus.text[imap->_responseStatus.text.length() - 2] == '\r') imap->_responseStatus.text[imap->_responseStatus.text.length() - 2] = 0; test = imap_responses[type].text; test.trim(); imap->_responseStatus.status = test; imap->_responseStatus.completed = true; } return status; } bool ESP_Mail_Client::parseCapabilityResponse(IMAPSession *imap, const char *buf, int &chunkIdx) { if (chunkIdx == 0) { MB_String res; // We add white space to make post token checking to work in all capabilities. // This will allow us to check "IDLE " and "ID " correctly. appendSpace(res, false, buf); if (strposP(res.c_str(), imap_responses[esp_mail_imap_response_capability_untagged].text, 0) > -1 || strposP(res.c_str(), imap_responses[esp_mail_imap_response_capability].text, 0) > -1) { for (int i = esp_mail_auth_capability_plain; i < esp_mail_auth_capability_maxType; i++) { if (strposP(res.c_str(), imap_auth_cap_pre_tokens[i].c_str(), 0) > -1) imap->_auth_capability[i] = true; } for (int i = esp_mail_imap_read_capability_imap4; i < esp_mail_imap_read_capability_maxType; i++) { if (strposP(res.c_str(), imap_read_cap_post_tokens[i].c_str(), 0) > -1) { imap->_feature_capability[i] = true; if (i == esp_mail_imap_read_capability_logindisable) imap->_auth_capability[esp_mail_auth_capability_login] = false; } } return true; } } return false; } char *ESP_Mail_Client::getList(char *buf, bool &isList) { if (buf[0] == '(' && buf[1] != ')') { if (buf[strlen(buf) - 1] == ')') buf[strlen(buf) - 1] = 0; else isList = true; return &buf[1]; } else if (isList) { if (buf[strlen(buf) - 1] == ')') { buf[strlen(buf) - 1] = 0; isList = false; } } return buf; } void ESP_Mail_Client::parseFoldersResponse(IMAPSession *imap, char *buf, bool list) { struct esp_mail_folder_info_t fd; int pos = list ? strposP(buf, imap_responses[esp_mail_imap_response_list].text, 0) : strposP(buf, imap_responses[esp_mail_imap_response_lsub].text, 0); bool isList = false, delimOk = false; if (pos != -1) { char *p = allocMem(strlen(buf)); strcpy(p, buf); char *pp = p; char *end = p; int count = 0; int tkPos = 3; while (pp != NULL) { // See RFC2047.h ESP_MAIL_STRSEP(&end, " "); count++; if (count >= tkPos && strlen(pp) > 0) { if (count == tkPos && pp[0] == '(' && pp[1] != ')') fd.attributes = getList(pp, isList); else if (isList) { fd.attributes += ' '; fd.attributes += getList(pp, isList); } else { if (pp[strlen(pp) - 1] == '"') pp[strlen(pp) - 1] = 0; const char *ptr = pp[0] == '"' ? &pp[1] : &pp[0]; if (!delimOk) { delimOk = true; fd.delimiter = ptr; } else { if (fd.name.length() > 0) fd.name += ' '; fd.name += ptr; } } } pp = end; } // release memory freeMem(&p); imap->_folders.add(fd); } } bool ESP_Mail_Client::parseIdleResponse(IMAPSession *imap) { int chunkBufSize = 0; if (!reconnect(imap)) return false; if (imap->client.connected()) chunkBufSize = imap->client.available(); else return false; if (chunkBufSize > 0) { chunkBufSize = ESP_MAIL_CLIENT_RESPONSE_BUFFER_SIZE; char *buf = allocMem(chunkBufSize + 1); int octetCount = 0; int readLen = 0; MB_String ovfBuf; readResponse(imap, buf, chunkBufSize, readLen, false, octetCount, ovfBuf); // If buffer overflown, copy from overflow buffer if (ovfBuf.length() > 0) { // release memory freeMem(&buf); buf = allocMem(ovfBuf.length() + 1); strcpy(buf, ovfBuf.c_str()); ovfBuf.clear(); } if (readLen > 0) { if (imap->_debug && imap->_debugLevel > esp_mail_debug_level_basic) esp_mail_debug_print((const char *)buf, true); char *tmp = nullptr; int p1 = -1; bool exists = false; tmp = subStr(buf, NULL, imap_responses[esp_mail_imap_response_exists].text, 0); if (tmp) { int numMsg = imap->_mbif._msgCount; imap->_mbif._msgCount = atoi(tmp); // release memory freeMem(&tmp); exists = true; imap->_mbif._folderChanged |= (int)imap->_mbif._msgCount != numMsg; if ((int)imap->_mbif._msgCount > numMsg) { imap->_mbif._polling_status.type = imap_polling_status_type_new_message; imap->_mbif._polling_status.messageNum = imap->_mbif._msgCount; } goto ex; } tmp = subStr(buf, NULL, imap_responses[esp_mail_imap_response_expunge].text, 0); if (tmp) { imap->_mbif._polling_status.type = imap_polling_status_type_remove_message; imap->_mbif._polling_status.messageNum = atoi(tmp); if (imap->_mbif._polling_status.messageNum == imap->_mbif._msgCount && imap->_mbif._nextUID > 0) imap->_mbif._nextUID--; // release memory freeMem(&tmp); imap->_mbif._folderChanged = true; goto ex; } tmp = subStr(buf, NULL, imap_responses[esp_mail_imap_response_recent].text, 0); if (tmp) { imap->_mbif._recentCount = atoi(tmp); // release memory freeMem(&tmp); goto ex; } tmp = subStr(buf, NULL, imap_responses[esp_mail_imap_response_fetch].text, 0); if (tmp) { imap->_mbif._polling_status.messageNum = atoi(tmp); // release memory freeMem(&tmp); imap->_mbif._polling_status.argument = buf; imap->_mbif._polling_status.argument.erase(0, p1 + 8); imap->_mbif._polling_status.argument.pop_back(); imap->_mbif._folderChanged = true; imap->_mbif._polling_status.type = imap_polling_status_type_fetch_message; goto ex; } ex: imap->_mbif._floderChangedState = (imap->_mbif._folderChanged && exists) || imap->_mbif._polling_status.type == imap_polling_status_type_fetch_message; } // release memory freeMem(&buf); } size_t imap_idle_tmo = imap->_imap_data->limit.imap_idle_timeout; if (imap_idle_tmo < 60 * 1000 || imap_idle_tmo > 29 * 60 * 1000) imap_idle_tmo = 10 * 60 * 1000; if (millis() - imap->_mbif._idleTimeMs > imap_idle_tmo) { if (imap->mStopListen(true)) return imap->mListen(true); return false; } return true; } void ESP_Mail_Client::appendFetchString(MB_String &buf, bool uid) { if (uid) buf += imap_cmd_post_tokens[esp_mail_imap_command_uid]; else joinStringSpace(buf, false, 2, imap_commands[esp_mail_imap_command_flags].text, esp_mail_str_38 /* "(" */); } void ESP_Mail_Client::parseCmdResponse(IMAPSession *imap, char *buf, PGM_P find) { if (imap->_imap_cmd == esp_mail_imap_cmd_get_uid) imap->_uid_tmp = 0; char *tmp = nullptr; int p1 = strposP(buf, find, 0); if (p1 != -1) { if (imap->_imap_cmd == esp_mail_imap_cmd_get_quota_root || imap->_imap_cmd == esp_mail_imap_cmd_get_acl || imap->_imap_cmd == esp_mail_imap_cmd_my_rights) { int ofs = imap->_imap_cmd == esp_mail_imap_cmd_get_quota_root ? 0 : 1; int p2 = strposP(buf, esp_mail_str_2 /* " " */, p1 + strlen_P(find) + ofs); if (p2 != -1) { int len = strlen(buf) - p2 - 1; tmp = allocMem(len); strncpy(tmp, buf + p2 + 1, strlen(buf) - p2 - 1); if (imap->_imap_cmd == esp_mail_imap_cmd_get_quota_root) { if (imap->_quota_root_tmp.length() > 0) imap->_quota_root_tmp += esp_mail_str_8; /* ";" */ imap->_quota_root_tmp += tmp; } else { imap->_acl_tmp = tmp; } // release memory freeMem(&tmp); } } else { int len = imap->_imap_cmd == esp_mail_imap_cmd_get_uid ? 20 : strlen(buf) - p1 - strlen_P(find); int ofs = imap->_imap_cmd == esp_mail_imap_cmd_get_uid || imap->_imap_cmd == esp_mail_imap_cmd_fetch_sequence_set ? 1 : imap->_imap_cmd == esp_mail_imap_cmd_namespace || imap->_imap_cmd == esp_mail_imap_cmd_id ? 0 : 2; tmp = allocMem(len); strncpy(tmp, buf + p1 + strlen_P(find), strlen(buf) - p1 - strlen_P(find) - ofs); esp_mail_imap_msg_num_t msg_num; switch ((int)imap->_imap_cmd) { case esp_mail_imap_cmd_get_uid: imap->_uid_tmp = atoi(tmp); break; case esp_mail_imap_cmd_get_flags: imap->_flags_tmp = tmp; break; case esp_mail_imap_cmd_get_quota: imap->_quota_tmp = tmp; break; case esp_mail_imap_cmd_id: imap->_server_id_tmp = tmp; break; case esp_mail_imap_cmd_namespace: imap->_ns_tmp += tmp; break; case esp_mail_imap_cmd_fetch_sequence_set: msg_num.type = esp_mail_imap_msg_num_type_uid; msg_num.value = (uint32_t)atoi(tmp); imap->_imap_msg_num.push_back(msg_num); if (imap->_imap_msg_num.size() > imap->_imap_data->limit.fetch) imap->_imap_msg_num.erase(imap->_imap_msg_num.begin()); break; default: break; } } // release memory freeMem(&tmp); } } bool ESP_Mail_Client::getFlags(IMAPSession *imap, char *buf, esp_mail_imap_response_types type) { if (strposP(buf, imap_responses[type].text, 0) != -1) { char *p = allocMem(strlen(buf)); strcpy(p, buf); char *pp = p; char *end = p; int count = 0; bool isList = false; int tkPos = (type == esp_mail_imap_response_permanent_flags) ? 4 : 3; while (pp != NULL) { // See RFC2047.h ESP_MAIL_STRSEP(&end, " "); count++; if (count >= tkPos && strlen(pp) > 0) { if (type == esp_mail_imap_response_permanent_flags && pp[strlen(pp) - 1] == ']') pp[strlen(pp) - 1] = 0; if (count == tkPos && pp[0] == '(' && pp[1] != ')') imap->_mbif.addFlag(getList(pp, isList), type == esp_mail_imap_response_permanent_flags); else if (isList) imap->_mbif.addFlag(getList(pp, isList), type == esp_mail_imap_response_permanent_flags); } pp = end; } // release memory freeMem(&p); return true; } return false; } void ESP_Mail_Client::parseExamineResponse(IMAPSession *imap, char *buf) { char *tmp = NULL; tmp = subStr(buf, NULL, imap_responses[esp_mail_imap_response_exists].text, 0); if (tmp) { imap->_mbif._msgCount = atoi(tmp); // release memory freeMem(&tmp); return; } tmp = subStr(buf, NULL, imap_responses[esp_mail_imap_response_recent].text, 0); if (tmp) { imap->_mbif._recentCount = atoi(tmp); // release memory freeMem(&tmp); return; } if (imap->_mbif._flags.size() == 0 && getFlags(imap, buf, esp_mail_imap_response_flags)) return; if (imap->_mbif._permanent_flags.size() == 0 && getFlags(imap, buf, esp_mail_imap_response_permanent_flags)) return; if (imap->_mbif._uidValidity == 0) { tmp = subStr(buf, imap_responses[esp_mail_imap_response_uidvalidity].text, esp_mail_str_41 /* "]" */, 0, 0); if (tmp) { imap->_mbif._uidValidity = atoi(tmp); // release memory freeMem(&tmp); return; } } if (imap->_nextUID.length() == 0) { tmp = subStr(buf, imap_responses[esp_mail_imap_response_uidnext].text, esp_mail_str_41 /* "]" */, 0, 0); if (tmp) { imap->_nextUID = tmp; imap->_mbif._nextUID = atoi(tmp); // release memory freeMem(&tmp); return; } } if (imap->_unseenMsgIndex.length() == 0) { tmp = subStr(buf, imap_responses[esp_mail_imap_response_unseen].text, esp_mail_str_41 /* "]" */, 0, 0); if (tmp) { imap->_unseenMsgIndex = tmp; imap->_mbif._unseenMsgIndex = atoi(tmp); // release memory freeMem(&tmp); return; } } if (imap->isCondStoreSupported()) { tmp = subStr(buf, imap_responses[esp_mail_imap_response_highest_modsec].text, esp_mail_str_41 /* "]" */, 0, 0); if (tmp) { imap->_mbif._highestModSeq = tmp; // release memory freeMem(&tmp); return; } tmp = subStr(buf, imap_responses[esp_mail_imap_response_nomodsec].text, nullptr, 0, 0); if (tmp) { imap->_mbif._nomodsec = true; // release memory freeMem(&tmp); return; } } } bool ESP_Mail_Client::handleIMAPError(IMAPSession *imap, int err, bool ret) { if (err < 0) { errorStatusCB(imap, nullptr, err, true); if (imap->_headers.size() > 0 && cHeader(imap)) { if ((imap->_imap_cmd == esp_mail_imap_cmd_fetch_body_attachment || imap->_imap_cmd == esp_mail_imap_cmd_fetch_body_inline) && (imap->_imap_data->download.attachment || imap->_imap_data->download.inlineImg)) { if (cPart(imap) && cHeader(imap)->part_headers.size() > 0) cPart(imap)->download_error = imap->errorReason().c_str(); } else if (cHeader(imap)) cHeader(imap)->error_msg = imap->errorReason().c_str(); cHeader(imap)->error = true; } } closeTCPSession(imap); imap->_cbData.empty(); return ret; } void ESP_Mail_Client::prepareFileList(IMAPSession *imap, MB_String &filePath) { #if defined(MBFS_SD_FS) if (!mbfs->longNameSupported()) { cHeader(imap)->sd_alias_file_count++; MB_String alias = cHeader(imap)->message_uid; alias += esp_mail_str_83; /* "_" */ alias += cHeader(imap)->sd_alias_file_count; if (imap->_sdFileList.length() > 0) { if (imap->_sdFileList[imap->_sdFileList.length() - 1] == ']') { imap->_sdFileList[imap->_sdFileList.length() - 1] = 0; imap->_sdFileList += esp_mail_str_8; /* "," */ } } imap->_sdFileList += esp_mail_str_80; /* "{\"Renamed\":\"" */ imap->_sdFileList += alias; imap->_sdFileList += esp_mail_str_81; /* "\",\"Original\":\"" */ imap->_sdFileList += filePath; imap->_sdFileList += esp_mail_str_82; /* "\"}]" */ // rename the original file filePath = alias; } #endif } bool ESP_Mail_Client::parseAttachmentResponse(IMAPSession *imap, char *buf, esp_mail_imap_response_data &res) { int bufLen = res.readLen; if (res.chunkIdx == 0) { char *tmp = subStr(buf, esp_mail_str_36 /* "{" */, esp_mail_str_37 /* "}" */, 0); if (tmp) { res.chunkIdx++; res.octetCount = 0; // CRLF counted from first line res.octetLength = atoi(tmp); // release memory freeMem(&tmp); cPart(imap)->octetLen = res.octetLength; cPart(imap)->octetCount = 0; cHeader(imap)->total_download_size += res.octetLength; imap->_lastProgress = -1; #if defined(ESP_MAIL_OTA_UPDATE_ENABLED) if (cPart(imap)->is_firmware_file) { cPart(imap)->is_firmware_file = Update.begin(cPart(imap)->attach_data_size); if (!cPart(imap)->is_firmware_file) { imap->_responseStatus.errorCode = IMAP_STATUS_FIRMWARE_UPDATE_INIT_FAILED; imap->_responseStatus.text.clear(); } #if !defined(SILENT_MODE) sendCallback(imap, esp_mail_cb_str_42 /* "Updating firmware..." */, true, false); if (!cPart(imap)->is_firmware_file) { printDebug(imap, imap->errorReason().c_str(), imap->errorReason().c_str(), esp_mail_debug_tag_type_error, true, false); } #endif } #endif if (!cPart(imap)->file_open_write) { if (imap->_storageReady && cPart(imap)->save_to_file) { res.downloadRequest = true; res.filePath.clear(); res.filePath += imap->_imap_data->storage.saved_path; res.filePath += esp_mail_str_10; /* "/" */ res.filePath += cHeader(imap)->message_uid; res.filePath += esp_mail_str_10; /* "/" */ res.filePath += cPart(imap)->filename; prepareFileList(imap, res.filePath); int sz = mbfs->open(res.filePath, mbfs_type imap->_imap_data->storage.type, mb_fs_open_mode_write); if (sz < 0) { imap->_responseStatus.errorCode = sz; imap->_responseStatus.text.clear(); #if !defined(SILENT_MODE) printDebug(imap, imap->errorReason().c_str(), imap->errorReason().c_str(), esp_mail_debug_tag_type_error, true, false); #endif } cPart(imap)->file_open_write = true; } } } return true; } yield_impl(); if (res.octetLength == 0) return true; if (cPart(imap)->octetCount <= res.octetLength) { if (cPart(imap)->octetCount + bufLen > res.octetLength) { bufLen = res.octetLength - cPart(imap)->octetCount; buf[bufLen] = 0; cPart(imap)->octetCount += bufLen; } else cPart(imap)->octetCount = res.octetCount; if (imap->_imap_data->enable.download_status) { if (imap->_debug) downloadReport(imap, 100 * cPart(imap)->octetCount / res.octetLength); } if (cPart(imap)->octetCount > res.octetLength) return true; bool write_error = false, fw_write_error = false; if (cPart(imap)->xencoding == esp_mail_msg_xencoding_base64) { size_t olen = 0; unsigned char *decoded = decodeBase64((const unsigned char *)buf, bufLen, &olen); if (decoded) { if (!cPart(imap)->sizeProp) { cPart(imap)->attach_data_size += olen; cHeader(imap)->total_attach_data_size += cPart(imap)->attach_data_size; } sendStreamCB(imap, (void *)decoded, olen, res.chunkIdx, false); size_t write = olen; if (cPart(imap)->is_firmware_file) { #if defined(ESP_MAIL_OTA_UPDATE_ENABLED) size_t fw_write = Update.write((uint8_t *)decoded, olen); cPart(imap)->firmware_downloaded_byte += fw_write == olen ? olen : 0; fw_write_error = fw_write != olen; #endif } if (cPart(imap)->save_to_file) { if (mbfs->ready(mbfs_type imap->_imap_data->storage.type)) write = mbfs->write(mbfs_type imap->_imap_data->storage.type, (uint8_t *)decoded, olen); } yield_impl(); // release memory freeMem(&decoded); write_error = write != olen; } if (!reconnect(imap)) return false; } else { // binary content if (!cPart(imap)->sizeProp) { cPart(imap)->attach_data_size += bufLen; cHeader(imap)->total_attach_data_size += cPart(imap)->attach_data_size; } sendStreamCB(imap, (void *)buf, bufLen, res.chunkIdx, false); int write = bufLen; if (cPart(imap)->is_firmware_file) { #if defined(ESP_MAIL_OTA_UPDATE_ENABLED) int fw_write = Update.write((uint8_t *)buf, bufLen); cPart(imap)->firmware_downloaded_byte += fw_write == bufLen ? (size_t)bufLen : 0; fw_write_error = fw_write != bufLen; #endif } if (cPart(imap)->save_to_file) { if (mbfs->ready(mbfs_type imap->_imap_data->storage.type)) write = mbfs->write(mbfs_type imap->_imap_data->storage.type, (uint8_t *)buf, bufLen); } yield_impl(); write_error = write != bufLen; if (!reconnect(imap)) return false; } #if defined(ESP_MAIL_OTA_UPDATE_ENABLED) if (cPart(imap)->is_firmware_file) { bool update_result_ok = !fw_write_error; if (!imap->_isFirmwareUpdated && update_result_ok && (cPart(imap)->firmware_downloaded_byte == (size_t)cPart(imap)->attach_data_size || cPart(imap)->octetCount >= res.octetLength)) { update_result_ok = Update.end(cPart(imap)->octetCount >= res.octetLength); if (update_result_ok) imap->_isFirmwareUpdated = true; } if (!update_result_ok) { cPart(imap)->is_firmware_file = false; imap->_responseStatus.errorCode = fw_write_error ? IMAP_STATUS_FIRMWARE_UPDATE_WRITE_FAILED : IMAP_STATUS_FIRMWARE_UPDATE_END_FAILED; imap->_responseStatus.text.clear(); #if !defined(SILENT_MODE) printDebug(imap, imap->_responseStatus.text.c_str(), imap->errorReason().c_str(), esp_mail_debug_tag_type_error, !imap->_debug, false); #endif } } #endif if (write_error || fw_write_error) return false; } res.chunkIdx++; return true; } void ESP_Mail_Client::downloadReport(IMAPSession *imap, int progress) { printProgress(progress, imap->_lastProgress); } void ESP_Mail_Client::fetchReport(IMAPSession *imap, int progress, bool download) { #if !defined(SILENT_MODE) if (imap->_debug && imap->_lastProgress == -1 && strcmp_P(cPart(imap)->filename.c_str(), esp_mail_str_84 /* "message" */) != 0) esp_mail_debug_print_tag(cPart(imap)->filename.c_str(), esp_mail_debug_tag_type_client, true); printProgress(progress, imap->_lastProgress); #endif } void ESP_Mail_Client::searchReport(IMAPSession *imap, int progress) { printProgress(progress, imap->_lastProgress); } struct esp_mail_imap_msg_num_t ESP_Mail_Client::cMSG(IMAPSession *imap) { return imap->_imap_msg_num[cIdx(imap)]; } int ESP_Mail_Client::cIdx(IMAPSession *imap) { return imap->_cMsgIdx; } void ESP_Mail_Client::decodeText(IMAPSession *imap, esp_mail_imap_response_data &res) { int bufLen = res.readLen; bool rfc822_body_subtype = cPart(imap)->message_sub_type == esp_mail_imap_message_sub_type_rfc822; if (res.chunkIdx == 0) { imap->_lastProgress = -1; char *tmp = subStr(res.response, esp_mail_str_36 /* "{" */, esp_mail_str_37 /* "}" */, 0); if (tmp) { res.chunkIdx++; res.octetCount = 0; res.octetLength = atoi(tmp); // release memory freeMem(&tmp); cPart(imap)->octetLen = res.octetLength; cPart(imap)->octetCount = 0; bool dlMsg = (rfc822_body_subtype && imap->_imap_data->download.rfc822) || (!rfc822_body_subtype && ((cPart(imap)->msg_type == esp_mail_msg_type_html && imap->_imap_data->download.html) || ((cPart(imap)->msg_type == esp_mail_msg_type_plain || cPart(imap)->msg_type == esp_mail_msg_type_enriched) && imap->_imap_data->download.text))); if (dlMsg) prepareFilePath(imap, res.filePath, false); if (res.filePath.length() == 0) { if (!rfc822_body_subtype) res.filePath += esp_mail_str_84; /* "message" */ else joinStringSpace(res.filePath, false, 2, esp_mail_str_85 /* "rfc822" */, esp_mail_str_84 /* "message" */); } cPart(imap)->filename = res.filePath; if (imap->_storageReady && !cPart(imap)->file_open_write && dlMsg) { prepareFileList(imap, res.filePath); int sz = mbfs->open(res.filePath, mbfs_type imap->_imap_data->storage.type, mb_fs_open_mode_write); if (sz > -1) { res.downloadRequest = true; cPart(imap)->file_open_write = true; } else { imap->_responseStatus.errorCode = sz; imap->_responseStatus.text.clear(); #if !defined(SILENT_MODE) if (imap->_debug) esp_mail_debug_print_tag(imap->errorReason().c_str(), esp_mail_debug_tag_type_error, true); #endif } } return; } else { #if !defined(SILENT_MODE) if (imap->_debug) esp_mail_debug_print_tag(esp_mail_error_imap_str_12 /* "no centent" */, esp_mail_debug_tag_type_client, false); #endif } } yield_impl(); if (res.octetLength == 0) return; bool enableDownloads = (imap->_imap_data->download.rfc822 && rfc822_body_subtype) || (!rfc822_body_subtype && ((cPart(imap)->msg_type == esp_mail_msg_type_html && imap->_imap_data->download.html) || ((cPart(imap)->msg_type == esp_mail_msg_type_plain || cPart(imap)->msg_type == esp_mail_msg_type_enriched) && imap->_imap_data->download.text))); if (imap->_imap_data->download.rfc822 || imap->_imap_data->download.html || imap->_imap_data->download.text || (rfc822_body_subtype && imap->_imap_data->enable.rfc822) || (!rfc822_body_subtype && ((cPart(imap)->msg_type == esp_mail_msg_type_html && imap->_imap_data->enable.html) || ((cPart(imap)->msg_type == esp_mail_msg_type_plain || cPart(imap)->msg_type == esp_mail_msg_type_enriched) && imap->_imap_data->enable.text)))) { if (cPart(imap)->octetCount + bufLen >= res.octetLength) { bufLen = res.octetLength - cPart(imap)->octetCount; cPart(imap)->octetCount += bufLen; if (res.octetLength <= res.octetCount && cPart(imap)->xencoding != esp_mail_msg_xencoding_base64 && cPart(imap)->xencoding != esp_mail_msg_xencoding_qp) bufLen = 0; res.response[bufLen] = 0; } else cPart(imap)->octetCount = res.octetCount; if (imap->_debug) fetchReport(imap, 100 * cPart(imap)->octetCount / res.octetLength, enableDownloads); if (cPart(imap)->octetCount <= res.octetLength) { bool hrdBrk = cPart(imap)->xencoding == esp_mail_msg_xencoding_qp; hrdBrk &= cPart(imap)->octetCount < res.octetLength; hrdBrk &= bufLen == 2 && res.response[bufLen - 2] != '\r' && res.response[bufLen - 1] != '\n'; // remove soft break for QP if (bufLen <= QP_ENC_MSG_LEN && res.response[bufLen - 1] == '=' && cPart(imap)->xencoding == esp_mail_msg_xencoding_qp) { hrdBrk = false; res.response[bufLen - 1] = 0; bufLen--; } size_t olen = 0; char *decoded = nullptr; MB_String str; bool dontDeleteOrModify = false; // decode the content based on the transfer decoding if (cPart(imap)->xencoding == esp_mail_msg_xencoding_base64) { decoded = (char *)decodeBase64((const unsigned char *)res.response, bufLen, &olen); } else if (cPart(imap)->xencoding == esp_mail_msg_xencoding_qp) { decoded = allocMem(bufLen + 10); decodeQP_UTF8(res.response, decoded); olen = strlen(decoded); } else if (cPart(imap)->xencoding == esp_mail_msg_xencoding_7bit) { decoded = decode7Bit_UTF8(res.response); olen = strlen(decoded); } else if (cPart(imap)->xencoding == esp_mail_msg_xencoding_8bit) { decoded = decode8Bit_UTF8(res.response); olen = strlen(decoded); } else { // binary dontDeleteOrModify = true; decoded = res.response; olen = bufLen; } if (decoded) { // charset? apply character decoding to the decoded or nonencoded content if ((rfc822_body_subtype && imap->_imap_data->enable.rfc822) || (!rfc822_body_subtype && ((cPart(imap)->msg_type == esp_mail_msg_type_html && imap->_imap_data->enable.html) || ((cPart(imap)->msg_type == esp_mail_msg_type_plain || cPart(imap)->msg_type == esp_mail_msg_type_enriched) && imap->_imap_data->enable.text)))) { if (imap->_charDecCallback) { IMAP_Decoding_Info decoding; decoding.charset = cPart(imap)->charset.c_str(); decoding.data = decoded; decoding.type = IMAP_Decoding_Info::message_part_type_text; imap->_charDecCallback(&decoding); if (decoding.decodedString.length() > 0) { char *buf2 = allocMem(decoding.decodedString.length() + 1); strcpy(buf2, decoding.decodedString.c_str()); if (decoded && !dontDeleteOrModify) // release memory freeMem(&decoded); decoded = buf2; olen = strlen(buf2); } } else { esp_mail_char_decoding_scheme scheme = getEncodingFromCharset(cPart(imap)->charset.c_str()); if (scheme == esp_mail_char_decoding_scheme_iso8859_1) { int ilen = olen; int olen2 = (ilen + 1) * 2; unsigned char *tmp = allocMem(olen2); decodeLatin1_UTF8(tmp, &olen2, (unsigned char *)decoded, &ilen); if (decoded && !dontDeleteOrModify) // release memory freeMem(&decoded); olen = olen2; decoded = (char *)tmp; } else if (scheme == esp_mail_char_decoding_scheme_tis_620 || scheme == esp_mail_char_decoding_scheme_iso8859_11 || scheme == esp_mail_char_decoding_scheme_windows_874) { char *out = allocMem((olen + 1) * 3); decodeTIS620_UTF8(out, decoded, olen); olen = strlen(out); if (decoded && !dontDeleteOrModify) // release memory freeMem(&decoded); decoded = out; } } if (cPart(imap)->text.length() < imap->_imap_data->limit.msg_size) { if (cPart(imap)->text.length() + olen < imap->_imap_data->limit.msg_size) { cPart(imap)->textLen += olen; cPart(imap)->text.append(decoded, olen); if (hrdBrk) { appendNewline(cPart(imap)->text); cPart(imap)->textLen += 2; } } else { int d = imap->_imap_data->limit.msg_size - cPart(imap)->text.length(); cPart(imap)->textLen += d; if (d > 0) cPart(imap)->text.append(decoded, d); if (hrdBrk) { appendNewline(cPart(imap)->text); cPart(imap)->textLen += 2; } } } } if (res.filePath.length() > 0 && res.downloadRequest) { if (mbfs->ready(mbfs_type imap->_imap_data->storage.type)) { if (olen > 0) mbfs->write(mbfs_type imap->_imap_data->storage.type, (uint8_t *)decoded, olen); if (hrdBrk) mbfs->write(mbfs_type imap->_imap_data->storage.type, (uint8_t *)"\r\n", 2); } } sendStreamCB(imap, (void *)decoded, olen, res.chunkIdx, hrdBrk); if (decoded && !dontDeleteOrModify) // release memory freeMem(&decoded); } } } res.chunkIdx++; } void ESP_Mail_Client::sendStreamCB(IMAPSession *imap, void *buf, size_t len, int chunkIndex, bool hrdBrk) { if (imap->_mimeDataStreamCallback && len > 0) { MIME_Data_Stream_Info streaminfo; streaminfo.uid = cHeader(imap)->message_uid; streaminfo.disposition = cPart(imap)->content_disposition.c_str(); streaminfo.type = cPart(imap)->content_type.c_str(); streaminfo.charSet = cPart(imap)->charset.c_str(); streaminfo.transfer_encoding = cPart(imap)->content_transfer_encoding.c_str(); streaminfo.cid = cPart(imap)->CID.c_str(); streaminfo.description = cPart(imap)->content_description.c_str(); streaminfo.date = cPart(imap)->creation_date.c_str(); streaminfo.filename = cPart(imap)->filename.c_str(); streaminfo.size = (cPart(imap)->sizeProp) ? cPart(imap)->attach_data_size : cPart(imap)->octetLen; streaminfo.name = cPart(imap)->name.c_str(); streaminfo.octet_size = cPart(imap)->octetLen; streaminfo.octet_count = cPart(imap)->octetCount; streaminfo.isFirstData = chunkIndex == 1; streaminfo.isLastData = !hrdBrk ? cPart(imap)->octetLen == cPart(imap)->octetCount : false; streaminfo.data_size = len; streaminfo.data = buf; streaminfo.flowed = cPart(imap)->plain_flowed; streaminfo.delsp = cPart(imap)->plain_delsp; imap->_mimeDataStreamCallback(streaminfo); if (hrdBrk) { streaminfo.isFirstData = false; streaminfo.isLastData = cPart(imap)->octetLen == cPart(imap)->octetCount; streaminfo.data_size = 2; streaminfo.data = (void *)"\r\n"; imap->_mimeDataStreamCallback(streaminfo); } } } void ESP_Mail_Client::prepareFilePath(IMAPSession *imap, MB_String &filePath, bool header) { bool rfc822_body_subtype = cPart(imap)->message_sub_type == esp_mail_imap_message_sub_type_rfc822; MB_String fpath = imap->_imap_data->storage.saved_path; fpath += esp_mail_str_10; /* "/" */ fpath += cHeader(imap)->message_uid; if (!header) { if (!rfc822_body_subtype) { fpath += esp_mail_str_86; /* "/msg" */ if (cPart(imap)->msg_type == esp_mail_msg_type_plain || cPart(imap)->msg_type == esp_mail_msg_type_enriched) fpath += mimeinfo[esp_mail_file_extension_txt].endsWith; else if (cPart(imap)->msg_type == esp_mail_msg_type_html) fpath += mimeinfo[esp_mail_file_extension_htm].endsWith; } else { fpath += esp_mail_str_87; /* "/rfc822_msg" */ if (cPart(imap)->rfc822_msg_Idx > 0) fpath += cPart(imap)->rfc822_msg_Idx; if (cPart(imap)->msg_type == esp_mail_msg_type_plain || cPart(imap)->msg_type == esp_mail_msg_type_enriched) fpath += mimeinfo[esp_mail_file_extension_txt].endsWith; else if (cPart(imap)->msg_type == esp_mail_msg_type_html) fpath += mimeinfo[esp_mail_file_extension_htm].endsWith; else // possible rfc822 encapsulated message which cannot fetch its header fpath += mimeinfo[esp_mail_file_extension_dat].endsWith; } } filePath = fpath; } void ESP_Mail_Client::sendStorageNotReadyError(IMAPSession *imap, esp_mail_file_storage_type storageType) { #if defined(MBFS_FLASH_FS) || defined(MBFS_SD_FS) #if !defined(SILENT_MODE) if (imap->_debug && (storageType == esp_mail_file_storage_type_flash || storageType == esp_mail_file_storage_type_sd)) { if (storageType == esp_mail_file_storage_type_flash) { esp_mail_debug_print_tag(esp_mail_error_mem_str_1 /* "flash Storage is not ready." */, esp_mail_debug_tag_type_error, true); #if defined(MB_ARDUINO_PICO) esp_mail_debug_print_tag(esp_mail_error_mem_str_10 /* "please make sure that the size of flash filesystem is not 0 in Pico." */, esp_mail_debug_tag_type_error, true); #endif } else if (storageType == esp_mail_file_storage_type_sd) esp_mail_debug_print_tag(esp_mail_error_mem_str_2 /* "SD Storage is not ready." */, esp_mail_debug_tag_type_error, true); } #endif #endif } IMAPSession::IMAPSession(Client *client) { setClient(client); } IMAPSession::IMAPSession() { } IMAPSession::~IMAPSession() { empty(); } bool IMAPSession::closeSession() { _prev_imap_cmd = esp_mail_imap_cmd_sasl_login; _prev_imap_custom_cmd = esp_mail_imap_cmd_custom; if (!connected()) return false; if (_mbif._idleTimeMs > 0) mStopListen(false); if (_loginStatus) { #if !defined(ESP8266) /** * The strange behavior in ESP8266 SSL client, BearSSLWiFiClientSecure * The client disposed without memory released after the server close * the connection due to LOGOUT command, which caused the memory leaks. */ if (!MailClient.imapLogout(this)) return false; #endif } return MailClient.handleIMAPError(this, 0, true); } bool IMAPSession::connected() { return client.connected(); } bool IMAPSession::connect(Session_Config *session_config, IMAP_Data *imap_data, bool login) { _sessionSSL = false; _sessionLogin = login; if (session_config) session_config->clearPorts(); this->_customCmdResCallback = NULL; int ptr = toAddr(*session_config); session_config->addPtr(&_configPtrList, ptr); if (!handleConnection(session_config, imap_data, _sessionSSL)) return false; if (!_sessionLogin) return true; _loginStatus = MailClient.imapAuth(this, _sessionSSL); return _loginStatus; } bool IMAPSession::mLogin(MB_StringPtr email, MB_StringPtr password, bool isToken) { if (_loginStatus) return true; if (!MailClient.sessionExisted(this)) return false; _session_cfg->login.email = email; _session_cfg->login.accessToken.clear(); _session_cfg->login.password.clear(); if (isToken) _session_cfg->login.accessToken = password; else _session_cfg->login.password = password; _loginStatus = MailClient.imapAuth(this, _sessionSSL); return _loginStatus; } void IMAPSession::appendIdList(MB_String &list, IMAP_Identification *identification) { if (identification->name.length() > 0 && identification->name.length() <= 1024) MailClient.appendImap4KeyValue(list, imap_identification_keys[esp_mail_imap_identification_key_name].text, identification->name.c_str()); if (identification->version.length() > 0 && identification->version.length() <= 1024) { if (list.length() > 0) MailClient.appendSpace(list); MailClient.appendImap4KeyValue(list, imap_identification_keys[esp_mail_imap_identification_key_version].text, identification->version.c_str()); } if (identification->os.length() > 0 && identification->os.length() <= 1024) { if (list.length() > 0) MailClient.appendSpace(list); MailClient.appendImap4KeyValue(list, imap_identification_keys[esp_mail_imap_identification_key_os].text, identification->os.c_str()); } if (identification->vendor.length() > 0 && identification->vendor.length() <= 1024) { if (list.length() > 0) MailClient.appendSpace(list); MailClient.appendImap4KeyValue(list, imap_identification_keys[esp_mail_imap_identification_key_vendor].text, identification->vendor.c_str()); } if (identification->support_url.length() > 0 && identification->support_url.length() <= 1024) { if (list.length() > 0) MailClient.appendSpace(list); MailClient.appendImap4KeyValue(list, imap_identification_keys[esp_mail_imap_identification_key_support_url].text, identification->support_url.c_str()); } if (identification->address.length() > 0 && identification->address.length() <= 1024) { if (list.length() > 0) MailClient.appendSpace(list); MailClient.appendImap4KeyValue(list, imap_identification_keys[esp_mail_imap_identification_key_address].text, identification->address.c_str()); } if (identification->date.length() > 0 && identification->date.length() <= 1024) { if (list.length() > 0) MailClient.appendSpace(list); MailClient.appendImap4KeyValue(list, imap_identification_keys[esp_mail_imap_identification_key_date].text, identification->date.c_str()); } if (identification->command.length() > 0 && identification->command.length() <= 1024) { if (list.length() > 0) MailClient.appendSpace(list); MailClient.appendImap4KeyValue(list, imap_identification_keys[esp_mail_imap_identification_key_command].text, identification->command.c_str()); } if (identification->arguments.length() > 0 && identification->arguments.length() <= 1024) { if (list.length() > 0) MailClient.appendSpace(list); MailClient.appendImap4KeyValue(list, imap_identification_keys[esp_mail_imap_identification_key_arguments].text, identification->arguments.c_str()); } if (identification->environment.length() > 0 && identification->environment.length() <= 1024) { if (list.length() > 0) MailClient.appendSpace(list); MailClient.appendImap4KeyValue(list, imap_identification_keys[esp_mail_imap_identification_key_environment].text, identification->environment.c_str()); } } bool IMAPSession::id(IMAP_Identification *identification) { #if !defined(SILENT_MODE) MailClient.printDebug(this, esp_mail_cb_str_61 /* "Send client identification..." */, esp_mail_dbg_str_82 /* "send IMAP command, ID" */, esp_mail_debug_tag_type_client, true, false); #endif if (!_feature_capability[esp_mail_imap_read_capability_id]) { #if !defined(SILENT_MODE) printDebugNotSupported(); #endif return false; } if (!MailClient.reconnect(this)) return false; MB_String cmd, idList; appendIdList(idList, identification); MailClient.appendSpace(cmd, true, imap_commands[esp_mail_imap_command_id].text); if (idList.length() > 0) MailClient.appendString(cmd, idList.c_str(), false, false, esp_mail_string_mark_type_round_bracket); else { int bufLen = 50; char *buf = MailClient.allocMem(bufLen); snprintf(buf, bufLen, pgm2Str(esp_mail_str_21 /* "(\"name\" \"ESP Mail Client\" \"version\" \"%s\")" */), ESP_MAIL_VERSION); cmd += buf; // release memory MailClient.freeMem(&buf); } if (MailClient.imapSend(this, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; _imap_cmd = esp_mail_imap_cmd_id; if (!MailClient.handleIMAPResponse(this, IMAP_STATUS_BAD_COMMAND, false)) return false; return true; } String IMAPSession::serverID() { return _server_id_tmp.c_str(); } bool IMAPSession::isAuthenticated() { return _authenticated; } bool IMAPSession::isLoggedIn() { return _loginStatus; } bool IMAPSession::isFirmwareUpdateSuccess() { return _isFirmwareUpdated; } bool IMAPSession::mCustomConnect(Session_Config *session_config, imapResponseCallback callback, MB_StringPtr tag) { this->_customCmdResCallback = callback; this->_responseStatus.tag = tag; bool ssl = false; if (!handleConnection(session_config, NULL, ssl)) return false; return true; } bool IMAPSession::handleConnection(Session_Config *session_config, IMAP_Data *imap_data, bool &ssl) { _session_cfg = session_config; if (!client.isInitialized()) return MailClient.handleIMAPError(this, TCP_CLIENT_ERROR_NOT_INITIALIZED, false); // Resources are also released if network disconnected. if (!MailClient.reconnect(this)) return false; // Need to close previous connection first to free resources. MailClient.closeTCPSession(this); _session_cfg = session_config; _imap_data = imap_data; MailClient.setCert(_session_cfg, _session_cfg->certificate.cert_data); ssl = false; if (!connect(ssl)) { MailClient.closeTCPSession(this); return false; } return true; } bool IMAPSession::connect(bool &ssl) { ssl = false; MB_String buf; if (_imap_data) { if (_imap_data->fetch.sequence_set.string.length() > 0 || _imap_data->fetch.uid.length() > 0 || _imap_data->fetch.number.length() > 0) _headerOnly = false; else _headerOnly = true; } _totalRead = 0; _secure = true; bool secureMode = true; client.txBufDivider = 16; // minimum, tx buffer size for ssl data and request command data client.rxBufDivider = 1; if (_imap_data) { if (!_headerOnly && !_imap_data->firmware_update.attach_filename.length() == 0 && !_imap_data->enable.html && !_imap_data->enable.text && !_imap_data->download.attachment && !_imap_data->download.inlineImg && !_imap_data->download.html && !_imap_data->download.text) client.rxBufDivider = 16; // minimum rx buffer size for only message header } MailClient.preparePortFunction(_session_cfg, false, _secure, secureMode, ssl); #if !defined(SILENT_MODE) MailClient.printLibInfo(this); #endif MailClient.prepareTime(_session_cfg, this); MailClient.setSecure(client, _session_cfg); if (!MailClient.beginConnection(_session_cfg, this, secureMode)) return false; // server connected client.setTimeout(tcpTimeout); // wait for greeting unsigned long dataMs = millis(); while (client.connected() && client.available() == 0 && millis() - dataMs < 2000) { yield_impl(); } int chunkBufSize = client.available(); if (chunkBufSize > 0) { char *buf = MailClient.allocMem(chunkBufSize + 1); client.readBytes(buf, chunkBufSize); if (_debug && _debugLevel > esp_mail_debug_level_basic && !_customCmdResCallback) esp_mail_debug_print((const char *)buf, true); if (_customCmdResCallback) { MailClient.imapResponseStatus(this, buf, esp_mail_str_3 /* "*" */); _responseStatus.text = buf; if (_responseStatus.text[_responseStatus.text.length() - 2] == '\r' && _responseStatus.text[_responseStatus.text.length() - 1] == '\n') _responseStatus.text[_responseStatus.text.length() - 2] = 0; if (_responseStatus.tag.length() == 0) this->_responseStatus.tag = esp_mail_imap_tag_str; _customCmdResCallback(_responseStatus); } // release memory MailClient.freeMem(&buf); } if (!_customCmdResCallback) { #if !defined(SILENT_MODE) MailClient.printDebug(this, esp_mail_cb_str_48 /* "IMAP server connected" */, esp_mail_dbg_str_33 /* "IMAP server connected" */, esp_mail_debug_tag_type_client, true, false); #endif } return true; } void IMAPSession::debug(int level) { if (level > esp_mail_debug_level_none) { if (level > esp_mail_debug_level_basic && level < esp_mail_debug_level_maintainer) level = esp_mail_debug_level_basic; _debugLevel = level; _debug = true; client.setDebugLevel(level); } else { _debugLevel = esp_mail_debug_level_none; _debug = false; client.setDebugLevel(0); } } String IMAPSession::errorReason() { return MailClient.errorReason(false, _responseStatus.errorCode, _responseStatus.text.c_str()); } int IMAPSession::errorCode() { return _responseStatus.errorCode; } bool IMAPSession::mSelectFolder(MB_StringPtr folderName, bool readOnly) { if (connected()) { if (!openFolder(folderName, readOnly)) return false; } else { _currentFolder = folderName; } if (!connected()) { _responseStatus.errorCode = IMAP_STATUS_OPEN_MAILBOX_FAILED; _responseStatus.clear(); } return connected(); } void IMAPSession::setTCPTimeout(unsigned long timeoutSec) { tcpTimeout = timeoutSec; } void IMAPSession::setClient(Client *client) { this->client.setClient(client); } void IMAPSession::setGSMClient(Client *client, void *modem, const char *pin, const char *apn, const char *user, const char *password) { this->client.setGSMClient(client, modem, pin, apn, user, password); } void IMAPSession::networkConnectionRequestCallback(NetworkConnectionRequestCallback networkConnectionCB) { this->client.networkConnectionRequestCallback(networkConnectionCB); } void IMAPSession::networkStatusRequestCallback(NetworkStatusRequestCallback networkStatusCB) { this->client.networkStatusRequestCallback(networkStatusCB); } void IMAPSession::setNetworkStatus(bool status) { this->client.setNetworkStatus(status); MailClient.networkStatus = status; } void IMAPSession::setSSLBufferSize(int rx, int tx) { this->client.setIOBufferSize(rx, tx); } bool IMAPSession::mOpenFolder(MB_StringPtr folderName, bool readOnly) { if (!connected()) { _responseStatus.errorCode = IMAP_STATUS_OPEN_MAILBOX_FAILED; _responseStatus.clear(); return false; } if (readOnly) return openMailbox(folderName, esp_mail_imap_auth_mode::esp_mail_imap_mode_examine, true, false); else return openMailbox(folderName, esp_mail_imap_auth_mode::esp_mail_imap_mode_select, true, false); } bool IMAPSession::getFolders(FoldersCollection &folders) { if (!connected()) return false; return getMailboxes(folders); } bool IMAPSession::mCloseFolder(bool expunge) { // no folder opened if (_currentFolder.length() == 0) { #if !defined(SILENT_MODE) MailClient.printDebug(this, esp_mail_error_imap_str_11 /* "no mailbox opened" */, esp_mail_error_imap_str_11 /* "no mailbox opened" */, esp_mail_debug_tag_type_client, true, false); #endif return false; } return closeMailbox(expunge); } bool IMAPSession::mListen(bool recon) { if (!MailClient.sessionExisted(this)) return false; if (_currentFolder.length() == 0) { _mbif._floderChangedState = false; _mbif._folderChanged = false; return false; } if (!MailClient.reconnect(this)) return false; if (!connected()) { if (!_imap_data || (millis() - _last_server_connect_ms < 2000 && _last_server_connect_ms > 0)) return false; _last_server_connect_ms = millis(); bool ssl = false; if (!connect(ssl)) { MailClient.closeTCPSession(this); return false; } // re-authenticate after session closed if (!MailClient.imapAuth(this, ssl)) { MailClient.closeTCPSession(this); return false; } // re-open folder if (!selectFolder(_currentFolder.c_str())) return false; } // no IDLE was not supported (should be checked after imapAuth) if (!_feature_capability[esp_mail_imap_read_capability_idle]) { #if !defined(SILENT_MODE) printDebugNotSupported(); #endif } if (_mbif._idleTimeMs == 0) { _mbif._polling_status.messageNum = 0; _mbif._polling_status.type = imap_polling_status_type_undefined; _mbif._polling_status.argument.clear(); _mbif._recentCount = 0; _mbif._folderChanged = false; #if !defined(SILENT_MODE) MB_String dbMsg; if (!recon) { dbMsg = esp_mail_dbg_str_51; /* "listening to " */ dbMsg += _currentFolder; dbMsg += esp_mail_dbg_str_52; /* " folder changes" */ MailClient.printDebug(this, esp_mail_cb_str_29 /* "Listening to mailbox changes..." */, dbMsg.c_str(), esp_mail_debug_tag_type_client, true, false); } #endif if (MailClient.imapSend(this, prependTag(imap_commands[esp_mail_imap_command_idle].text).c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; _imap_cmd = esp_mail_imap_cmd_idle; if (!MailClient.handleIMAPResponse(this, IMAP_STATUS_BAD_COMMAND, false)) return false; #if !defined(SILENT_MODE) if (!recon) { dbMsg = esp_mail_str_88 /* "polling established on " */; dbMsg += MailClient.Time.getDateTimeString(MailClient.Time.getCurrentTimestamp(), "%B %d, %Y %H:%M:%S"); MailClient.printDebug(this, esp_mail_cb_str_49 /* "Polling established" */, dbMsg.c_str(), esp_mail_debug_tag_type_client, true, false); } #endif _mbif._idleTimeMs = millis(); } else { if (_mbif._floderChangedState) { _mbif._floderChangedState = false; _mbif._folderChanged = false; _mbif._polling_status.messageNum = 0; _mbif._polling_status.type = imap_polling_status_type_undefined; _mbif._polling_status.argument.clear(); _mbif._recentCount = 0; } size_t imap_idle_tmo = _imap_data->limit.imap_idle_timeout; if (imap_idle_tmo < 60 * 1000 || imap_idle_tmo > 29 * 60 * 1000) imap_idle_tmo = 10 * 60 * 1000; size_t host_check_interval = _imap_data->limit.imap_idle_host_check_interval; if (host_check_interval < 30 * 1000 || host_check_interval > imap_idle_tmo) host_check_interval = 60 * 1000; if (millis() - _last_host_check_ms > host_check_interval && connected()) { _last_host_check_ms = millis(); IPAddress ip; if (client.hostByName(_session_cfg->server.host_name.c_str(), ip) != 1) { closeSession(); _mbif._idleTimeMs = millis(); return false; } } return MailClient.parseIdleResponse(this); } return true; } bool IMAPSession::mStopListen(bool recon) { _mbif._idleTimeMs = 0; _mbif._floderChangedState = false; _mbif._folderChanged = false; _mbif._polling_status.messageNum = 0; _mbif._polling_status.type = imap_polling_status_type_undefined; _mbif._polling_status.argument.clear(); _mbif._recentCount = 0; if (!connected() || _currentFolder.length() == 0 || !_feature_capability[esp_mail_imap_read_capability_idle]) return false; if (MailClient.imapSend(this, imap_commands[esp_mail_imap_command_done].text, true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; _imap_cmd = esp_mail_imap_cmd_done; if (!MailClient.handleIMAPResponse(this, IMAP_STATUS_BAD_COMMAND, false)) return false; #if !defined(SILENT_MODE) if (!recon) { MailClient.printDebug(this, esp_mail_cb_str_50 /* "Mailbox listening stopped" */, esp_mail_dbg_str_54 /* "Mailbox listening stopped" */, esp_mail_debug_tag_type_client, true, false); } #endif return true; } bool IMAPSession::folderChanged() { return _mbif._floderChangedState; } bool IMAPSession::noop() { #if !defined(SILENT_MODE) MailClient.printDebug(this, esp_mail_cb_str_62 /* "Sending noop..." */, esp_mail_dbg_str_83 /* "send IMAP command, NOOP" */, esp_mail_debug_tag_type_client, true, false); #endif if (MailClient.imapSend(this, prependTag(imap_commands[esp_mail_imap_command_noop].text).c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; _imap_cmd = esp_mail_imap_cmd_noop; if (!MailClient.handleIMAPResponse(this, IMAP_STATUS_BAD_COMMAND, false)) return false; return true; } void IMAPSession::checkUID() { if (MailClient.strcmpP(_imap_data->fetch.uid.c_str(), 0, imap_commands[esp_mail_imap_command_uid].text) || MailClient.strcmpP(_imap_data->fetch.uid.c_str(), 0, imap_commands[esp_mail_imap_command_flags].text) || MailClient.strcmpP(_imap_data->fetch.uid.c_str(), 0, imap_commands[esp_mail_imap_command_body].text) || MailClient.strcmpP(_imap_data->fetch.uid.c_str(), 0, imap_commands[esp_mail_imap_command_peek].text) || MailClient.strcmpP(_imap_data->fetch.uid.c_str(), 0, imap_commands[esp_mail_imap_command_text].text) || MailClient.strcmpP(_imap_data->fetch.uid.c_str(), 0, imap_commands[esp_mail_imap_command_header].text) || MailClient.strcmpP(_imap_data->fetch.uid.c_str(), 0, imap_commands[esp_mail_imap_command_fields].text) || MailClient.strcmpP(_imap_data->fetch.uid.c_str(), 0, esp_mail_str_40 /* "[" */) || MailClient.strcmpP(_imap_data->fetch.uid.c_str(), 0, esp_mail_str_41 /* "]" */) || MailClient.strcmpP(_imap_data->fetch.uid.c_str(), 0, imap_commands[esp_mail_imap_command_mime].text)) _imap_data->fetch.uid = esp_mail_str_3; /* "*" */ } void IMAPSession::checkPath() { MB_String path = _imap_data->storage.saved_path; if (path[0] != '/') { path = '/'; path += _imap_data->storage.saved_path; path = path.c_str(); } } bool IMAPSession::headerOnly() { return _headerOnly; } struct esp_mail_imap_msg_list_t IMAPSession::data() { struct esp_mail_imap_msg_list_t ret; for (size_t i = 0; i < _headers.size(); i++) { #if defined(MB_ARDUINO_ESP) || defined(MB_ARDUINO_PICO) if (MailClient.getFreeHeap() < ESP_MAIL_MIN_MEM) continue; #endif struct esp_mail_imap_msg_item_t itm; itm.setRFC822Headers(&_headers[i].header_fields); itm.UID = _headers[i].message_uid; itm.msgNo = _headers[i].message_no; itm.flags = _headers[i].flags.c_str(); itm.acceptLang = _headers[i].accept_language.c_str(); itm.contentLang = _headers[i].content_language.c_str(); itm.hasAttachment = _headers[i].hasAttachment; itm.fetchError = _headers[i].error_msg.c_str(); getMessages(i, itm); getRFC822Messages(i, itm); ret.msgItems.push_back(itm); } return ret; } SelectedFolderInfo IMAPSession::selectedFolder() { return _mbif; } void IMAPSession::callback(imapStatusCallback imapCallback) { _statusCallback = imapCallback; } void IMAPSession::characterDecodingCallback(imapCharacterDecodingCallback callback) { _charDecCallback = callback; } void IMAPSession::mimeDataStreamCallback(MIMEDataStreamCallback mimeDataStreamCallback) { _mimeDataStreamCallback = mimeDataStreamCallback; } void IMAPSession::setSystemTime(time_t ts, float gmtOffset) { MailClient.Time.setTimestamp(ts, gmtOffset); } void IMAPSession::keepAlive(int tcpKeepIdleSeconds, int tcpKeepIntervalSeconds, int tcpKeepCount) { this->client.keepAlive(tcpKeepIdleSeconds, tcpKeepIntervalSeconds, tcpKeepCount); } bool IMAPSession::isKeepAlive() { return this->client.isKeepAlive(); } void IMAPSession::getMessages(uint16_t messageIndex, struct esp_mail_imap_msg_item_t &msg) { msg.text.content = ""; msg.text.charSet = ""; msg.text.content_type = ""; msg.text.transfer_encoding = ""; msg.html.content = ""; msg.html.charSet = ""; msg.html.content_type = ""; msg.html.transfer_encoding = ""; if (messageIndex < _headers.size()) { int sz = _headers[messageIndex].part_headers.size(); if (sz > 0) { for (int i = 0; i < sz; i++) { if (_headers[messageIndex].part_headers[i].attach_type == esp_mail_att_type_attachment || (!_headers[messageIndex].part_headers[i].rfc822_part && _headers[messageIndex].part_headers[i].message_sub_type != esp_mail_imap_message_sub_type_rfc822)) { if (_headers[messageIndex].part_headers[i].attach_type == esp_mail_att_type_none) { if (_headers[messageIndex].part_headers[i].msg_type == esp_mail_msg_type_plain || _headers[messageIndex].part_headers[i].msg_type == esp_mail_msg_type_enriched) { msg.text.content = _headers[messageIndex].part_headers[i].text.c_str(); msg.text.charSet = _headers[messageIndex].part_headers[i].charset.c_str(); msg.text.content_type = _headers[messageIndex].part_headers[i].content_type.c_str(); msg.text.transfer_encoding = _headers[messageIndex].part_headers[i].content_transfer_encoding.c_str(); } if (_headers[messageIndex].part_headers[i].msg_type == esp_mail_msg_type_html) { msg.html.content = _headers[messageIndex].part_headers[i].text.c_str(); msg.html.charSet = _headers[messageIndex].part_headers[i].charset.c_str(); msg.html.content_type = _headers[messageIndex].part_headers[i].content_type.c_str(); msg.html.transfer_encoding = _headers[messageIndex].part_headers[i].content_transfer_encoding.c_str(); } } else { struct esp_mail_attachment_info_t att; att.filename = _headers[messageIndex].part_headers[i].filename.c_str(); att.mime = _headers[messageIndex].part_headers[i].content_type.c_str(); att.name = _headers[messageIndex].part_headers[i].name.c_str(); att.size = _headers[messageIndex].part_headers[i].attach_data_size; att.description = _headers[messageIndex].part_headers[i].content_description.c_str(); att.creationDate = _headers[messageIndex].part_headers[i].creation_date.c_str(); att.type = _headers[messageIndex].part_headers[i].attach_type; msg.attachments.push_back(att); } } } } } } void IMAPSession::getRFC822Messages(uint16_t messageIndex, struct esp_mail_imap_msg_item_t &msg) { if (messageIndex < _headers.size()) { int size = _headers[messageIndex].part_headers.size(); int partIdx = 0; int cIdx = 0; IMAP_MSG_Item *_rfc822 = nullptr; if (size > 0) { for (int i = 0; i < size; i++) { if (_headers[messageIndex].part_headers[i].message_sub_type == esp_mail_imap_message_sub_type_rfc822 && _headers[messageIndex].part_headers[i].attach_type != esp_mail_att_type_attachment) { if (_headers[messageIndex].part_headers[i].rfc822_part) { if (partIdx > 0) msg.rfc822.push_back(*_rfc822); cIdx = i; partIdx++; _rfc822 = new IMAP_MSG_Item(); _rfc822->setRFC822Headers(&_headers[messageIndex].part_headers[i].rfc822_header); } else { if (MailClient.multipartMember(_headers[messageIndex].part_headers[cIdx].partNumStr, _headers[messageIndex].part_headers[i].partNumStr)) { if (_headers[messageIndex].part_headers[i].attach_type == esp_mail_att_type_none) { if (_headers[messageIndex].part_headers[i].msg_type == esp_mail_msg_type_plain || _headers[messageIndex].part_headers[i].msg_type == esp_mail_msg_type_enriched) { _rfc822->text.charSet = _headers[messageIndex].part_headers[i].charset.c_str(); _rfc822->text.content_type = _headers[messageIndex].part_headers[i].content_type.c_str(); _rfc822->text.content = _headers[messageIndex].part_headers[i].text.c_str(); _rfc822->text.transfer_encoding = _headers[messageIndex].part_headers[i].content_transfer_encoding.c_str(); } if (_headers[messageIndex].part_headers[i].msg_type == esp_mail_msg_type_html) { _rfc822->html.charSet = _headers[messageIndex].part_headers[i].charset.c_str(); _rfc822->html.content_type = _headers[messageIndex].part_headers[i].content_type.c_str(); _rfc822->html.content = _headers[messageIndex].part_headers[i].text.c_str(); _rfc822->html.transfer_encoding = _headers[messageIndex].part_headers[i].content_transfer_encoding.c_str(); } } else { struct esp_mail_attachment_info_t att; att.filename = _headers[messageIndex].part_headers[i].filename.c_str(); att.mime = _headers[messageIndex].part_headers[i].content_type.c_str(); att.name = _headers[messageIndex].part_headers[i].name.c_str(); att.size = _headers[messageIndex].part_headers[i].attach_data_size; att.description = _headers[messageIndex].part_headers[i].content_description.c_str(); att.creationDate = _headers[messageIndex].part_headers[i].creation_date.c_str(); att.type = _headers[messageIndex].part_headers[i].attach_type; _rfc822->attachments.push_back(att); } } } } } if ((int)msg.rfc822.size() < partIdx && _rfc822 != nullptr) msg.rfc822.push_back(*_rfc822); } } } bool IMAPSession::closeMailbox(bool expunge) { #if !defined(SILENT_MODE) MB_String dbMsg = esp_mail_dbg_str_32; /* "closing the " */ dbMsg += _currentFolder; dbMsg += esp_mail_str_89; /* " folder..." */ MailClient.printDebug(this, esp_mail_cb_str_27 /* "Closing the mailbox folder..." */, dbMsg.c_str(), esp_mail_debug_tag_type_client, true, false); #endif if (!MailClient.reconnect(this)) return false; // If folder was opened in readonly mode, use CLOSE command will not expunge the deleted messages // Or user intent to expunge the deleted message after close the folder. if (expunge || _readOnlyMode) { if (MailClient.imapSend(this, prependTag(imap_commands[esp_mail_imap_command_close].text).c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; _imap_cmd = esp_mail_imap_cmd_close; if (!MailClient.handleIMAPResponse(this, IMAP_STATUS_CLOSE_MAILBOX_FAILED, false)) return false; } else { // Close folder without expunge if (_feature_capability[esp_mail_imap_read_capability_unselect]) { if (MailClient.imapSend(this, prependTag(imap_commands[esp_mail_imap_command_unselect].text).c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; _imap_cmd = esp_mail_imap_cmd_unselect; if (!MailClient.handleIMAPResponse(this, IMAP_STATUS_CLOSE_MAILBOX_FAILED, false)) return false; } else { // Open non-existing folder MB_String folder = esp_mail_str_84 /* "message" */; folder += MailClient.Time.getCurrentTimestamp(); MB_StringPtr folderPtr; folderPtr = toStringPtr(folder); openMailbox(folderPtr, esp_mail_imap_auth_mode::esp_mail_imap_mode_examine, true, true); } } _currentFolder.clear(); _mailboxOpened = false; return true; } bool IMAPSession::openMailbox(MB_StringPtr folder, esp_mail_imap_auth_mode mode, bool waitResponse, bool unselect) { if (!MailClient.sessionExisted(this)) return false; MB_String _folder = folder; if (!MailClient.reconnect(this)) return false; if (_folder.length() == 0) return false; bool _dbg = _debug; imapStatusCallback _cb = _statusCallback; // The SELECT/EXAMINE command automatically deselects any currently selected mailbox // before attempting the new selection (RFC3501 p.33) // folder should not close for re-selection otherwise the server returned * BAD Command Argument Error. 12 if (!unselect) { bool sameFolder = strcmp(_currentFolder.c_str(), _folder.c_str()) == 0; // guards 3 seconds to prevent accidently frequently select the same folder with the same mode if (!_mailboxOpened && sameFolder && millis() - _lastSameFolderOpenMillis < 3000) { if ((_readOnlyMode && mode == esp_mail_imap_mode_examine) || (!_readOnlyMode && mode == esp_mail_imap_mode_select)) return true; } if (!sameFolder) _currentFolder = folder; #if !defined(SILENT_MODE) MB_String dbMsg = esp_mail_dbg_str_68; /* "selecting the " */ dbMsg += _currentFolder; dbMsg += esp_mail_str_89; /* " folder..." */ MailClient.printDebug(this, esp_mail_cb_str_51 /* "Open the mailbox folder..." */, dbMsg.c_str(), esp_mail_debug_tag_type_client, true, false); #endif } else { // Hide the callback and debug info _debug = false; _statusCallback = NULL; _currentFolder = folder; } MB_String cmd; MailClient.appendSpace(cmd, true, mode == esp_mail_imap_mode_examine ? imap_commands[esp_mail_imap_command_examine].text : imap_commands[esp_mail_imap_command_select].text); MailClient.appendString(cmd, _currentFolder.c_str(), false, false, esp_mail_string_mark_type_double_quote); if (isCondStoreSupported()) { MailClient.appendSpace(cmd); MailClient.appendString(cmd, imap_commands[esp_mail_imap_command_condstore].text, false, false, esp_mail_string_mark_type_round_bracket); } _imap_cmd = mode == esp_mail_imap_mode_examine ? esp_mail_imap_cmd_examine : esp_mail_imap_cmd_select; if (MailClient.imapSend(this, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; if (!unselect) _lastSameFolderOpenMillis = millis(); if (waitResponse && !MailClient.handleIMAPResponse(this, IMAP_STATUS_OPEN_MAILBOX_FAILED, false)) { if (!unselect) return false; } if (unselect) { _statusCallback = _cb; _debug = _dbg; } if (mode == esp_mail_imap_mode_examine) _readOnlyMode = true; else if (mode == esp_mail_imap_mode_select) _readOnlyMode = false; _mailboxOpened = !unselect; return true; } bool IMAPSession::getMailboxes(FoldersCollection &folders) { #if !defined(SILENT_MODE) MailClient.printDebug(this, esp_mail_cb_str_16 /* "Reading the list of mailboxes..." */, esp_mail_dbg_str_35 /* "send IMAP command, LIST" */, esp_mail_debug_tag_type_client, true, false); #endif _folders.clear(); MB_String cmd; MailClient.appendSpace(cmd, true, imap_commands[esp_mail_imap_command_list].text); MailClient.appendString(cmd, NULL, false, false, esp_mail_string_mark_type_double_quote); MailClient.prependSpace(cmd, esp_mail_str_3 /* "*" */); if (MailClient.imapSend(this, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; _imap_cmd = esp_mail_imap_cmd_list; if (!MailClient.handleIMAPResponse(this, IMAP_STATUS_LIST_MAILBOXS_FAILED, false)) return false; folders = _folders; return true; } bool IMAPSession::mGetSubscribesMailboxes(MB_StringPtr reference, MB_StringPtr mailbox, FoldersCollection &folders) { #if !defined(SILENT_MODE) MailClient.printDebug(this, esp_mail_cb_str_30 /* "Listing the subscribed mailboxes..." */, esp_mail_dbg_str_56 /* "send IMAP command, LSUB" */, esp_mail_debug_tag_type_client, true, false); #endif folders.clear(); MB_String cmd; MailClient.appendSpace(cmd, true, imap_commands[esp_mail_imap_command_lsub].text); MailClient.appendString(cmd, MB_String(reference).c_str(), false, false, esp_mail_string_mark_type_double_quote); MailClient.appendSpace(cmd); MailClient.appendString(cmd, MB_String(mailbox).c_str(), false, false, esp_mail_string_mark_type_double_quote); if (MailClient.imapSend(this, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; FoldersCollection tmp; for (size_t i = 0; i < this->_folders.size(); i++) tmp.add(this->_folders[i]); this->_folders.clear(); _imap_cmd = esp_mail_imap_cmd_lsub; if (!MailClient.handleIMAPResponse(this, IMAP_STATUS_LIST_MAILBOXS_FAILED, false)) return false; for (size_t i = 0; i < this->_folders.size(); i++) folders.add(this->_folders[i]); this->_folders.clear(); for (size_t i = 0; i < tmp.size(); i++) this->_folders.add(tmp[i]); tmp.clear(); return true; } bool IMAPSession::mSubscribe(MB_StringPtr folder) { #if !defined(SILENT_MODE) MailClient.printDebug(this, esp_mail_cb_str_31 /* "Subscribe mailbox..." */, esp_mail_dbg_str_57 /* "send IMAP command, SUBSCRIBE" */, esp_mail_debug_tag_type_client, true, false); #endif MB_String cmd; MailClient.appendSpace(cmd, true, imap_commands[esp_mail_imap_command_subscribe].text); MailClient.appendString(cmd, MB_String(folder).c_str(), false, false, esp_mail_string_mark_type_double_quote); if (MailClient.imapSend(this, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; _imap_cmd = esp_mail_imap_cmd_subscribe; if (!MailClient.handleIMAPResponse(this, IMAP_STATUS_LIST_MAILBOXS_FAILED, false)) return false; return true; } bool IMAPSession::mUnSubscribe(MB_StringPtr folder) { #if !defined(SILENT_MODE) MailClient.printDebug(this, esp_mail_cb_str_32 /* "Unsubscribe mailbox..." */, esp_mail_dbg_str_58 /* "send IMAP command, UNSUBSCRIBE" */, esp_mail_debug_tag_type_client, true, false); #endif MB_String cmd; MailClient.appendSpace(cmd, true, imap_commands[esp_mail_imap_command_unsubscribe].text); MailClient.appendString(cmd, MB_String(folder).c_str(), false, false, esp_mail_string_mark_type_double_quote); if (MailClient.imapSend(this, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; _imap_cmd = esp_mail_imap_cmd_unsubscribe; if (!MailClient.handleIMAPResponse(this, IMAP_STATUS_LIST_MAILBOXS_FAILED, false)) return false; return true; } bool IMAPSession::mFetchSequenceSet() { MB_String cmd; if (_imap_data->fetch.sequence_set.UID) MailClient.appendSpace(cmd, true, 2, imap_commands[esp_mail_imap_command_uid].text, imap_commands[esp_mail_imap_command_fetch].text); else MailClient.appendSpace(cmd, true, imap_commands[esp_mail_imap_command_fetch].text); MailClient.appendSpace(cmd, false, _imap_data->fetch.sequence_set.string.c_str()); MailClient.appendString(cmd, imap_commands[esp_mail_imap_command_uid].text, false, false, esp_mail_string_mark_type_round_bracket); addModifier(cmd, esp_mail_imap_command_changedsince, _imap_data->fetch.modsequence); if (MailClient.imapSend(this, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; _imap_msg_num.clear(); _imap_cmd = esp_mail_imap_cmd_fetch_sequence_set; if (!MailClient.handleIMAPResponse(this, IMAP_STATUS_LIST_MAILBOXS_FAILED, false)) return false; return true; } MB_String IMAPSession::prependTag(PGM_P cmd, PGM_P tag) { MB_String s = (tag == NULL) ? esp_mail_imap_tag_str : tag; MailClient.appendSpace(s); s += cmd; return s; } bool IMAPSession::checkCapabilities() { #if !defined(SILENT_MODE) MailClient.printDebug(this, esp_mail_cb_str_52 /* "Checking the capability..." */, esp_mail_dbg_str_76 /* "check the capability" */, esp_mail_debug_tag_type_client, true, false); #endif if (MailClient.imapSend(this, prependTag(imap_commands[esp_mail_imap_command_capability].text).c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; _imap_cmd = esp_mail_imap_cmd_capability; if (!MailClient.handleIMAPResponse(this, IMAP_STATUS_CHECK_CAPABILITIES_FAILED, false)) return false; return true; } bool IMAPSession::mCreateFolder(MB_StringPtr folderName) { #if !defined(SILENT_MODE) MailClient.printDebug(this, esp_mail_cb_str_59 /* "Creating folder..." */, esp_mail_dbg_str_49 /* "creating folder" */, esp_mail_debug_tag_type_client, true, false); #endif MB_String cmd; MailClient.joinStringSpace(cmd, true, 2, imap_commands[esp_mail_imap_command_create].text, folderName); if (MailClient.imapSend(this, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; _imap_cmd = esp_mail_imap_cmd_create; if (!MailClient.handleIMAPResponse(this, IMAP_STATUS_BAD_COMMAND, false)) return false; return true; } bool IMAPSession::mRenameFolder(MB_StringPtr currentFolderName, MB_StringPtr newFolderName) { #if !defined(SILENT_MODE) MailClient.printDebug(this, esp_mail_cb_str_53 /* "Renaming folder..." */, esp_mail_dbg_str_55 /* "renaming folder" */, esp_mail_debug_tag_type_client, true, false); #endif MB_String o = currentFolderName; MB_String n = newFolderName; o.trim(); n.trim(); if (o == n) return true; if (o.length() == 0 || n.length() == 0) return false; MB_String cmd; MailClient.appendSpace(cmd, true, imap_commands[esp_mail_imap_command_rename].text); MailClient.appendString(cmd, o.c_str(), false, false, esp_mail_string_mark_type_double_quote); MailClient.appendSpace(cmd); MailClient.appendString(cmd, n.c_str(), false, false, esp_mail_string_mark_type_double_quote); if (MailClient.imapSend(this, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; _imap_cmd = esp_mail_imap_cmd_rename; if (!MailClient.handleIMAPResponse(this, IMAP_STATUS_BAD_COMMAND, false)) return false; if (_currentFolder == o) selectFolder(n.c_str(), false); return true; } int IMAPSession::getUID(int msgNum) { #if !defined(SILENT_MODE) MailClient.printDebug(this, esp_mail_cb_str_25 /* "Get UID..." */, esp_mail_dbg_str_79 /* "get UID" */, esp_mail_debug_tag_type_client, true, false); #endif int uid = mGetUID(msgNum); if (!MailClient.sessionExisted(this)) return false; #if !defined(SILENT_MODE) MB_String dbMsg = esp_mail_cb_str_54; /* "UID is " */ dbMsg += uid; MailClient.printDebug(this, dbMsg.c_str(), dbMsg.c_str(), esp_mail_debug_tag_type_client, true, false); #endif return uid; } int IMAPSession::mGetUID(int msgNum) { if (_currentFolder.length() == 0) return 0; MB_String cmd; MailClient.joinStringSpace(cmd, true, 3, imap_commands[esp_mail_imap_command_fetch].text, MB_String(msgNum).c_str(), imap_commands[esp_mail_imap_command_uid].text); if (MailClient.imapSend(this, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return 0; _imap_cmd = esp_mail_imap_cmd_get_uid; if (!MailClient.handleIMAPResponse(this, IMAP_STATUS_BAD_COMMAND, false)) return 0; return _uid_tmp; } const char *IMAPSession::getFlags(int msgNum) { #if !defined(SILENT_MODE) MailClient.printDebug(this, esp_mail_cb_str_55 /* "Get Flags..." */, esp_mail_dbg_str_80 /* "get flags" */, esp_mail_debug_tag_type_client, true, false); #endif _flags_tmp.clear(); if (_currentFolder.length() == 0) return _flags_tmp.c_str(); MB_String cmd, cmd2; MailClient.appendSpace(cmd, true, imap_commands[esp_mail_imap_command_fetch].text); MailClient.appendString(cmd2, imap_commands[esp_mail_imap_command_flags].text, false, false, esp_mail_string_mark_type_round_bracket); MailClient.joinStringSpace(cmd, false, 2, MB_String(msgNum).c_str(), cmd2.c_str()); if (MailClient.imapSend(this, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return _flags_tmp.c_str(); _imap_cmd = esp_mail_imap_cmd_get_flags; MailClient.handleIMAPResponse(this, IMAP_STATUS_BAD_COMMAND, false); return _flags_tmp.c_str(); } bool IMAPSession::mSendCustomCommand(MB_StringPtr cmd, imapResponseCallback callback, MB_StringPtr tag) { _customCmdResCallback = callback; _cmd = cmd; _cmd.trim(); MB_String _tag = cmd; _tag.trim(); MB_String _tag2 = tag; _tag2.trim(); if (_tag2.length() == 0) { int p = MailClient.strpos(_tag.c_str(), " ", 0); if (p > -1) { _tag.erase(p, _tag.length() - p); _tag.trim(); _responseStatus.tag = _tag; } } else { _responseStatus.tag = tag; _responseStatus.tag.trim(); if (MailClient.strpos(_cmd.c_str(), _responseStatus.tag.c_str(), 0, false) == -1) _cmd = prependTag(_cmd.c_str(), _responseStatus.tag.c_str()); } // filter for specific command if (_cmd.find(imap_cmd_pre_tokens[esp_mail_imap_command_idle].c_str()) != MB_String::npos) _imap_custom_cmd = esp_mail_imap_cmd_idle; else if (_cmd.find(imap_cmd_pre_tokens[esp_mail_imap_command_append].c_str()) != MB_String::npos) _imap_custom_cmd = esp_mail_imap_cmd_append; else if (_cmd.find(imap_cmd_pre_tokens[esp_mail_imap_command_login].c_str()) != MB_String::npos) _imap_custom_cmd = esp_mail_imap_cmd_sasl_login; else if (_cmd.find(imap_cmd_pre_tokens[esp_mail_imap_command_logout].c_str()) != MB_String::npos) _imap_custom_cmd = esp_mail_imap_cmd_logout; else _imap_custom_cmd = esp_mail_imap_cmd_custom; if (_prev_imap_custom_cmd != _imap_custom_cmd || _imap_custom_cmd != esp_mail_imap_cmd_idle) { if (MailClient.imapSend(this, _cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) { _prev_imap_custom_cmd = esp_mail_imap_cmd_custom; return false; } } _imap_cmd = esp_mail_imap_cmd_custom; if (!MailClient.handleIMAPResponse(this, IMAP_STATUS_BAD_COMMAND, false)) { _prev_imap_custom_cmd = esp_mail_imap_cmd_custom; return false; } if (_imap_custom_cmd == esp_mail_imap_cmd_sasl_login) { _authenticated = true; _loginStatus = true; } else if (_imap_custom_cmd == esp_mail_imap_cmd_logout) { _authenticated = false; _loginStatus = false; } _prev_imap_custom_cmd = _imap_custom_cmd; if (MailClient.strposP(_cmd.c_str(), imap_auth_capabilities[esp_mail_auth_capability_starttls].text, 0, false) == 0) { bool verify = false; if (_session_cfg) verify = _session_cfg->certificate.verify; if (!client.connectSSL(verify)) return false; // set the secure mode if (_session_cfg) { // We reset the prefer connection mode in case user set it. _session_cfg->secure.startTLS = false; _session_cfg->secure.mode = esp_mail_secure_mode_undefined; } _secure = true; } return true; } bool IMAPSession::mSendData(MB_StringPtr data, bool lastData, esp_mail_imap_command cmd) { MB_String _data = data; if (MailClient.imapSend(this, _data.c_str(), lastData) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; if (lastData) { _imap_cmd = cmd; _cmd.clear(); if (!MailClient.handleIMAPResponse(this, IMAP_STATUS_BAD_COMMAND, false)) return false; } return true; } bool IMAPSession::mSendData(uint8_t *data, size_t size, bool lastData, esp_mail_imap_command cmd) { if (MailClient.imapSend(this, data, size) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; if (lastData) { if (MailClient.imapSend(this, pgm2Str(esp_mail_str_42 /* "\r\n" */), false) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; _imap_cmd = cmd; _cmd.clear(); if (!MailClient.handleIMAPResponse(this, IMAP_STATUS_BAD_COMMAND, false)) return false; } return true; } bool IMAPSession::mDeleteFolder(MB_StringPtr folderName) { #if !defined(SILENT_MODE) MailClient.printDebug(this, esp_mail_cb_str_56 /* "Deleting folder..." */, esp_mail_dbg_str_81 /* "delete folder" */, esp_mail_debug_tag_type_client, true, false); #endif MB_String cmd; MailClient.joinStringSpace(cmd, true, 2, imap_commands[esp_mail_imap_command_delete].text, MB_String(folderName).c_str()); if (MailClient.imapSend(this, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; _imap_cmd = esp_mail_imap_cmd_delete; if (!MailClient.handleIMAPResponse(this, IMAP_STATUS_BAD_COMMAND, false)) return false; return true; } bool IMAPSession::isCondStoreSupported() { return _feature_capability[esp_mail_imap_read_capability_condstore]; } bool IMAPSession::isModseqSupported() { return isCondStoreSupported() && !_mbif._nomodsec; } void IMAPSession::addModifier(MB_String &cmd, esp_mail_imap_command_types type, int32_t modsequence) { if (modsequence > -1 && isModseqSupported()) { MB_String modifier; MailClient.joinStringSpace(modifier, false, 2, imap_commands[type].text, MB_String(modsequence).c_str()); MailClient.appendSpace(cmd); MailClient.appendString(cmd, modifier.c_str(), false, false, esp_mail_string_mark_type_round_bracket); } } bool IMAPSession::deleteMsg(MessageList *toDelete, const char *sequenceSet, bool UID, bool expunge, int32_t modsequence) { if ((toDelete && toDelete->_list.size() == 0) || (!toDelete && strlen(sequenceSet) == 0)) return false; if (!selectFolder(_currentFolder.c_str(), false)) return false; #if !defined(SILENT_MODE) MailClient.printDebug(this, esp_mail_cb_str_57 /* "Deleting message(s)..." */, esp_mail_dbg_str_75 /* "delete message(s)" */, esp_mail_debug_tag_type_client, true, false); #endif if (!MailClient.sessionExisted(this)) return false; MB_String cmd; if (UID || toDelete) { MailClient.appendSpace(cmd, true, 2, imap_commands[esp_mail_imap_command_uid].text, imap_commands[esp_mail_imap_command_store].text); if (toDelete && toDelete->_list.size() > 0) MailClient.appendList(cmd, toDelete->_list); } else if (!toDelete && strlen(sequenceSet) > 0) MailClient.appendSpace(cmd, true, imap_commands[esp_mail_imap_command_store].text); if (!toDelete && strlen(sequenceSet) > 0) cmd += sequenceSet; addModifier(cmd, esp_mail_imap_command_unchangedsince, modsequence); cmd += imap_cmd_pre_tokens[esp_mail_imap_command_plus_flags]; MailClient.prependDot(cmd, imap_commands[esp_mail_imap_command_silent].text); MailClient.appendSpace(cmd); MailClient.appendString(cmd, esp_mail_str_91 /* "\\Deleted" */, false, false, esp_mail_string_mark_type_round_bracket); if (MailClient.imapSend(this, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; _imap_cmd = esp_mail_imap_cmd_store; if (!MailClient.handleIMAPResponse(this, IMAP_STATUS_BAD_COMMAND, false)) return false; if (expunge) { if (MailClient.imapSend(this, prependTag(imap_commands[esp_mail_imap_command_expunge].text).c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; _imap_cmd = esp_mail_imap_cmd_expunge; if (!MailClient.handleIMAPResponse(this, IMAP_STATUS_BAD_COMMAND, false)) return false; } return true; } bool IMAPSession::mDeleteMessages(MessageList *toDelete, bool expunge, int32_t modsequence) { if (toDelete->_list.size() > 0) return deleteMsg(toDelete, "", false, expunge); return true; } bool IMAPSession::mDeleteMessagesSet(MB_StringPtr sequenceSet, bool UID, bool expunge, int32_t modsequence) { return deleteMsg(nullptr, MB_String(sequenceSet).c_str(), UID, expunge); } bool IMAPSession::copyMsg(MessageList *toCopy, const char *sequenceSet, bool UID, MB_StringPtr dest) { #if !defined(SILENT_MODE) MB_String dbMsg = esp_mail_dbg_str_48; /* "copying message(s) to " */ dbMsg += dest; MailClient.printDebug(this, esp_mail_cb_str_58 /* "Copying message(s)..." */, dbMsg.c_str(), esp_mail_debug_tag_type_client, true, false); #endif if (!MailClient.sessionExisted(this)) return false; if ((toCopy && toCopy->_list.size() == 0) || (!toCopy && strlen(sequenceSet) == 0)) return false; if (!selectFolder(_currentFolder.c_str(), false)) return false; MB_String cmd; if (UID || toCopy) { MailClient.appendSpace(cmd, true, 2, imap_commands[esp_mail_imap_command_uid].text, imap_commands[esp_mail_imap_command_copy].text); if (toCopy && toCopy->_list.size() > 0) MailClient.appendList(cmd, toCopy->_list); } else if (!toCopy && strlen(sequenceSet) > 0) MailClient.appendSpace(cmd, true, imap_commands[esp_mail_imap_command_copy].text); if (!toCopy && strlen(sequenceSet) > 0) cmd += sequenceSet; MailClient.prependSpace(cmd, MB_String(dest).c_str()); if (MailClient.imapSend(this, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; _imap_cmd = esp_mail_imap_cmd_copy; if (!MailClient.handleIMAPResponse(this, IMAP_STATUS_BAD_COMMAND, false)) return false; return true; } bool IMAPSession::mCopyMessages(MessageList *toCopy, MB_StringPtr dest) { return copyMsg(toCopy, "", false, dest); } bool IMAPSession::mCopyMessagesSet(MB_StringPtr sequenceSet, bool UID, MB_StringPtr dest) { return copyMsg(nullptr, MB_String(sequenceSet).c_str(), UID, dest); } bool IMAPSession::moveMsg(MessageList *toMove, const char *sequenceSet, bool UID, MB_StringPtr dest) { #if !defined(SILENT_MODE) MB_String dbMsg = esp_mail_dbg_str_59; /* "moving message(s) to " */ dbMsg += dest; MailClient.printDebug(this, esp_mail_cb_str_60 /* "Moving message(s)..." */, dbMsg.c_str(), esp_mail_debug_tag_type_client, true, false); #endif if (!MailClient.sessionExisted(this)) return false; if ((toMove && toMove->_list.size() == 0) || (!toMove && strlen(sequenceSet) == 0)) return false; if (!_feature_capability[esp_mail_imap_read_capability_move]) { bool ret = mCopyMessages(toMove, dest); if (ret) ret = mDeleteMessages(toMove, true); return ret; } if (!selectFolder(_currentFolder.c_str(), false)) return false; MB_String cmd; if (UID || toMove) { MailClient.appendSpace(cmd, true, 2, imap_commands[esp_mail_imap_command_uid].text, imap_commands[esp_mail_imap_command_move].text); if (toMove && toMove->_list.size() > 0) MailClient.appendList(cmd, toMove->_list); } else if (!toMove && strlen(sequenceSet) > 0) MailClient.appendSpace(cmd, true, imap_commands[esp_mail_imap_command_move].text); if (!toMove && strlen(sequenceSet) > 0) cmd += sequenceSet; MailClient.prependSpace(cmd, MB_String(dest).c_str()); if (MailClient.imapSend(this, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; _imap_cmd = esp_mail_imap_cmd_move; if (!MailClient.handleIMAPResponse(this, IMAP_STATUS_BAD_COMMAND, false)) return false; return true; } bool IMAPSession::mMoveMessages(MessageList *toMove, MB_StringPtr dest) { return moveMsg(toMove, "", false, dest); } bool IMAPSession::mMoveMessagesSet(MB_StringPtr sequenceSet, bool UID, MB_StringPtr dest) { return moveMsg(nullptr, MB_String(sequenceSet).c_str(), UID, dest); } bool IMAPSession::mGetSetQuota(MB_StringPtr quotaRoot, IMAP_Quota_Root_Info *data, bool getMode) { #if !defined(SILENT_MODE) PGM_P p1 = getMode ? esp_mail_cb_str_33 /* "Get quota root resource usage and limit..." */ : esp_mail_cb_str_34 /* "Set quota root resource usage and limit..." */; PGM_P p2 = getMode ? esp_mail_dbg_str_60 /* "send IMAP command, GETQUOTA" */ : esp_mail_dbg_str_61 /* "send IMAP command, SETQUOTA" */; MailClient.printDebug(this, p1, p2, esp_mail_debug_tag_type_client, true, false); #endif if (!_feature_capability[esp_mail_imap_read_capability_quota]) { #if !defined(SILENT_MODE) printDebugNotSupported(); #endif return false; } MB_String _quotaRoot = quotaRoot; MB_String cmd; if (getMode) { MailClient.appendSpace(cmd, true, imap_commands[esp_mail_imap_command_getquota].text); MailClient.appendString(cmd, _quotaRoot.c_str(), false, false, esp_mail_string_mark_type_double_quote); } else { MailClient.appendSpace(cmd, true, imap_commands[esp_mail_imap_command_setquota].text); MailClient.appendString(cmd, _quotaRoot.c_str(), false, false, esp_mail_string_mark_type_double_quote); MailClient.appendSpace(cmd); MB_String cmd2; MailClient.joinStringSpace(cmd2, false, 2, data->name.c_str(), MB_String((int)data->limit).c_str()); MailClient.appendString(cmd, cmd2.c_str(), false, false, esp_mail_string_mark_type_round_bracket); } if (MailClient.imapSend(this, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; _quota_tmp.clear(); _imap_cmd = (getMode) ? esp_mail_imap_cmd_get_quota : esp_mail_imap_cmd_set_quota; if (!MailClient.handleIMAPResponse(this, IMAP_STATUS_BAD_COMMAND, false)) return false; if (getMode) { mParseQuota(_quota_tmp.c_str(), data); } _quota_tmp.clear(); return true; } void IMAPSession::mParseQuota(const char *quota, IMAP_Quota_Root_Info *data) { _vectorImpl tokens; MailClient.splitToken(quota, tokens, " "); data->quota_root = tokens[0]; tokens[1].erase(0, 1); data->name = tokens[1]; data->usage = atoi(tokens[2].c_str()); data->limit = atoi(tokens[3].c_str()); } bool IMAPSession::mGetQuotaRoots(MB_StringPtr mailbox, IMAP_Quota_Roots_List *quotaRootsList) { #if !defined(SILENT_MODE) MailClient.printDebug(this, esp_mail_cb_str_35 /* "Get the list of quota roots..." */, esp_mail_dbg_str_62 /* "send IMAP command, GETQUOTAROOT" */, esp_mail_debug_tag_type_client, true, false); #endif if (!MailClient.sessionExisted(this)) return false; if (!_feature_capability[esp_mail_imap_read_capability_quota]) { #if !defined(SILENT_MODE) printDebugNotSupported(); #endif return false; } MB_String _mailbox = mailbox; MB_String cmd; MailClient.appendSpace(cmd, true, 2, imap_commands[esp_mail_imap_command_getquota].text, imap_commands[esp_mail_imap_command_root].text); MailClient.appendString(cmd, _mailbox.c_str(), false, false, esp_mail_string_mark_type_double_quote); if (MailClient.imapSend(this, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; _quota_root_tmp.clear(); _quota_tmp.clear(); _imap_cmd = esp_mail_imap_cmd_get_quota_root; if (!MailClient.handleIMAPResponse(this, IMAP_STATUS_BAD_COMMAND, false)) return false; _vectorImpl tokens; MailClient.splitToken(_quota_root_tmp.c_str(), tokens, ","); for (size_t i = 0; i < tokens.size(); i++) { _vectorImpl tk; MailClient.splitToken(tokens[i].c_str(), tk, ":"); IMAP_Quota_Root_Info data; if (tk.size() > 1) mParseQuota(tk[1].c_str(), &data); else data.quota_root = tk[0]; quotaRootsList->add(data); } _quota_root_tmp.clear(); _quota_tmp.clear(); return true; } bool IMAPSession::mManageACL(MB_StringPtr mailbox, IMAP_Rights_List *acl_list, IMAP_Rights_Info *acl, MB_StringPtr identifier, esp_mail_imap_command type) { #if !defined(SILENT_MODE) PGM_P p1 = NULL; PGM_P p2 = NULL; if (type == esp_mail_imap_cmd_get_acl) { p1 = esp_mail_cb_str_36; /* "Get the ACL..." */ p2 = esp_mail_dbg_str_77; /* "get the ACL" */ } else if (type == esp_mail_imap_cmd_set_acl) { p1 = esp_mail_cb_str_37; /* "Setting the ACL..." */ p2 = esp_mail_dbg_str_78; /* "set the ACL" */ } else if (type == esp_mail_imap_cmd_delete_acl) { p1 = esp_mail_cb_str_38; /* "Deleting the ACL..." */ p2 = esp_mail_dbg_str_72; /* "delete the ACL" */ } else if (type == esp_mail_imap_cmd_my_rights) { p1 = esp_mail_cb_str_39; /* "Get my ACL..." */ p2 = esp_mail_dbg_str_23; /* "get my ACL" */ } MailClient.printDebug(this, p1, p2, esp_mail_debug_tag_type_client, true, false); #endif if (!MailClient.sessionExisted(this)) return false; if (!_feature_capability[esp_mail_imap_read_capability_acl]) { #if !defined(SILENT_MODE) printDebugNotSupported(); #endif return false; } MB_String _mailbox = mailbox; MB_String _identifier = identifier; MB_String cmd; if (type == esp_mail_imap_cmd_get_acl) { MailClient.appendSpace(cmd, true, imap_commands[esp_mail_imap_command_get_acl].text); MailClient.appendString(cmd, _mailbox.c_str(), false, false, esp_mail_string_mark_type_double_quote); } else if (type == esp_mail_imap_cmd_set_acl) { MailClient.appendSpace(cmd, true, imap_commands[esp_mail_imap_command_set_acl].text); MailClient.appendString(cmd, MB_String(mailbox).c_str(), false, false, esp_mail_string_mark_type_double_quote); MailClient.appendSpace(cmd); MailClient.appendString(cmd, acl->identifier.c_str(), false, false, esp_mail_string_mark_type_double_quote); MailClient.appendSpace(cmd); MB_String rights; getRights(rights, acl); MailClient.appendString(cmd, rights.c_str(), false, false, esp_mail_string_mark_type_double_quote); } else if (type == esp_mail_imap_cmd_delete_acl) { MailClient.appendSpace(cmd, true, imap_commands[esp_mail_imap_command_delete_acl].text); MailClient.appendString(cmd, _mailbox.c_str(), false, false, esp_mail_string_mark_type_double_quote); MailClient.appendSpace(cmd); MailClient.appendString(cmd, _identifier.c_str(), false, false, esp_mail_string_mark_type_double_quote); } else if (type == esp_mail_imap_cmd_my_rights) { MailClient.appendSpace(cmd, true, imap_commands[esp_mail_imap_command_myrights].text); MailClient.appendString(cmd, _mailbox.c_str(), false, false, esp_mail_string_mark_type_double_quote); } if (MailClient.imapSend(this, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; _acl_tmp.clear(); _imap_cmd = type; if (!MailClient.handleIMAPResponse(this, IMAP_STATUS_BAD_COMMAND, false)) return false; if (type == esp_mail_imap_cmd_get_acl) parseACL(_acl_tmp, acl_list); else if (type == esp_mail_imap_cmd_my_rights) parseRights(_acl_tmp, acl); _acl_tmp.clear(); return true; } void IMAPSession::parseACL(MB_String &acl_str, IMAP_Rights_List *right_list) { _vectorImpl tokens; MailClient.splitToken(acl_str.c_str(), tokens, " "); for (size_t i = 0; i < tokens.size(); i += 2) { IMAP_Rights_Info info; info.identifier = tokens[i]; parseRights(tokens[i + 1], &info); right_list->add(info); } } void IMAPSession::parseRights(MB_String &righs_str, IMAP_Rights_Info *info) { for (size_t i = 0; i < righs_str.length(); i++) { uint8_t c = righs_str[i] - 97; if (c >= esp_mail_imap_rights_administer && c < esp_mail_imap_rights_maxType) info->rights[c] = true; } } void IMAPSession::getRights(MB_String &righs_str, IMAP_Rights_Info *info) { for (size_t i = esp_mail_imap_rights_administer; i < esp_mail_imap_rights_maxType; i++) { if (info->rights[i]) righs_str += (char)(i + 97); } } bool IMAPSession::mNamespace(IMAP_Namespaces_List *ns) { #if !defined(SILENT_MODE) MailClient.printDebug(this, esp_mail_cb_str_40 /* "Get namespace..." */, esp_mail_dbg_str_67 /* "send IMAP command, NAMESPACE" */, esp_mail_debug_tag_type_client, true, false); #endif if (!MailClient.sessionExisted(this)) return false; if (!_feature_capability[esp_mail_imap_read_capability_namespace]) { #if !defined(SILENT_MODE) printDebugNotSupported(); #endif return false; } if (MailClient.imapSend(this, prependTag(imap_commands[esp_mail_imap_command_namespace].text).c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; _ns_tmp.clear(); _imap_cmd = esp_mail_imap_cmd_namespace; if (!MailClient.handleIMAPResponse(this, IMAP_STATUS_BAD_COMMAND, false)) return false; int cnt = 0; MB_String personal, other, shared; for (size_t i = 0; i < _ns_tmp.length(); i++) { if (i > 0 && i < _ns_tmp.length() - 1 && _ns_tmp[i] == ' ' && _ns_tmp[i - 1] != '"' && _ns_tmp[i + 1] != '"') { if (cnt == 0) personal = _ns_tmp.substr(0, i); else if (cnt == 1) { other = _ns_tmp.substr(personal.length() + 1, i - personal.length() - 1); shared = _ns_tmp.substr(i + 1, _ns_tmp.length() - i - 1); } cnt++; } } if (personal.length() > 4) parseNamespaces(personal, &(ns->personal_namespaces)); if (other.length() > 4) parseNamespaces(other, &(ns->other_users_namespaces)); if (shared.length() > 4) parseNamespaces(shared, &(ns->shared_namespaces)); return true; } bool IMAPSession::mEnable(MB_StringPtr capability) { #if !defined(SILENT_MODE) MailClient.printDebug(this, esp_mail_cb_str_41 /* "Enable capability..." */, esp_mail_dbg_str_68 /* "send IMAP command, ENABLE" */, esp_mail_debug_tag_type_client, true, false); #endif if (!_feature_capability[esp_mail_imap_read_capability_enable]) { #if !defined(SILENT_MODE) printDebugNotSupported(); #endif return false; } MB_String _cap = capability; MB_String cmd; MailClient.joinStringSpace(cmd, true, 2, imap_commands[esp_mail_imap_command_enable].text, _cap.c_str()); if (MailClient.imapSend(this, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED) return false; _imap_cmd = esp_mail_imap_cmd_enable; if (!MailClient.handleIMAPResponse(this, IMAP_STATUS_BAD_COMMAND, false)) return false; return true; } void IMAPSession::parseNamespaces(MB_String &ns_str, IMAP_Namespaces *ns) { MB_String tmp = ns_str.substr(2, ns_str.length() - 4); tmp.replaceAll(")(", " "); _vectorImpl tokens; MailClient.splitToken(tmp.c_str(), tokens, " "); for (size_t i = 0; i < tokens.size(); i += 2) { IMAP_Namespace_Info info; info.prefix = tokens[i]; info.delimiter = tokens[i + 1]; ns->add(info); } } void IMAPSession::empty() { _nextUID.clear(); _unseenMsgIndex.clear(); _flags_tmp.clear(); _quota_tmp.clear(); _quota_root_tmp.clear(); _acl_tmp.clear(); _ns_tmp.clear(); _server_id_tmp.clear(); _sdFileList.clear(); clearMessageData(); } IMAP_Status IMAPSession::status() { return _cbData; } String IMAPSession::fileList() { return _sdFileList.c_str(); } void IMAPSession::clearMessageData() { for (size_t i = 0; i < _headers.size(); i++) { _headers[i].part_headers.clear(); } _headers.clear(); _imap_msg_num.clear(); _mbif._searchCount = 0; _flags_tmp.clear(); #if defined(MB_USE_STD_VECTOR) _folders.clear(); _mbif._flags.clear(); #endif } void IMAPSession::printDebugNotSupported() { #if !defined(SILENT_MODE) MailClient.printDebug(this, esp_mail_error_imap_str_13 /* "not supported by IMAP server" */, esp_mail_error_imap_str_13 /* "not supported by IMAP server" */, esp_mail_debug_tag_type_error, true, false); #endif } IMAP_Status::IMAP_Status() { } IMAP_Status::~IMAP_Status() { empty(); } const char *IMAP_Status::info() { return _info.c_str(); } bool IMAP_Status::success() { return _success; } void IMAP_Status::empty() { _info.clear(); } #endif #endif /* ESP_MAIL_IMAP_H */