Tasmota/lib/libesp32/ESP-Mail-Client/src/ESP_Mail_SMTP.h
s-hadinger c2f8821b2f
Sendmail upgraded to ESP-Mail-Client v3.4.9 from v1.2.0, using BearSSL instead of MbedTLS (#19460)
* `Sendmail` upgraded to ESP-Mail-Client v3.4.9 from v1.2.0, using BearSSL instead of MbedTLS

* Fix compilation on ESP8266

* Fix compilation

* fix compilation
2023-09-04 23:00:37 +02:00

3632 lines
126 KiB
C

#ifndef ESP_MAIL_SMTP_H
#define ESP_MAIL_SMTP_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_SMTP)
void ESP_Mail_Client::mimeFromFile(const char *name, MB_String &mime)
{
MB_String ext = name;
size_t p = ext.find_last_of(".");
if (p != MB_String::npos)
{
ext = ext.substr(p, ext.length() - p);
if (ext.length() > 0)
getMIME(ext.c_str(), mime);
}
}
void ESP_Mail_Client::getMIME(const char *ext, MB_String &mime)
{
mime.clear();
for (int i = 0; i < esp_mail_file_extension_maxType; i++)
{
if (strcmp_P(ext, mimeinfo[i].endsWith) == 0)
{
mime = mimeinfo[i].mimeType;
break;
}
}
}
bool ESP_Mail_Client::smtpAuth(SMTPSession *smtp, bool &ssl)
{
if (!smtp)
return false;
smtp->_auth_capability[esp_mail_auth_capability_login] = false;
smtp->_session_cfg->int_start_tls = smtp->_session_cfg->secure.startTLS;
smtp->_session_cfg->int_mode = smtp->_session_cfg->secure.mode;
#if !defined(ESP_MAIL_DISABLE_SSL)
initial_stage:
#endif
// Sending greeting helo response
#if !defined(SILENT_MODE)
printDebug<SMTPSession *>(smtp,
esp_mail_cb_str_3 /* "Sending greeting response..." */,
esp_mail_dbg_str_5 /* "send SMTP command, EHLO" */,
esp_mail_debug_tag_type_client,
true,
false);
#endif
// The Extended HELLO (EHLO) and HELLO (HELO) commands are used to identify Client (ourself)
// Since we support ESMTP (rfc5321), let server knows by sending EHLO first
// If server was not support ESMTP (rfc5321) the failure 501, 500, 502, or 550 would be replied.
// To prevent connection rejection, EHLO/HELO command parameter should be primary host name (domain name) of client system.
// Otherwise client public IP address string (IPv4 or IPv6) can be assign when no host name is available
MB_String s = smtp_cmd_post_tokens[esp_mail_smtp_command_ehlo];
appendDomain(s, smtp->_session_cfg->login.user_domain.c_str());
if (smtpSend(smtp, s.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED)
return false;
// expected success status code 250
// expected error status code 500, 501, 504, 421
if (!handleSMTPResponse(smtp, esp_mail_smtp_cmd_greeting, esp_mail_smtp_status_code_250, 0))
{
#if !defined(SILENT_MODE)
if (smtp->_debug)
esp_mail_debug_print_tag(esp_mail_dbg_str_17 /* "No ESMTP supported, send SMTP command, HELO" */, esp_mail_debug_tag_type_client, true);
#endif
// In case EHLO (rfc5321) is not acceptable,
// send HELO command (rfc821) instead.
s = smtp_cmd_post_tokens[esp_mail_smtp_command_helo];
appendDomain(s, smtp->_session_cfg->login.user_domain.c_str());
if (!smtpSend(smtp, s.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED)
return false;
// expected success status code 250
// expected error status code 500, 501, 504, 421
if (!handleSMTPResponse(smtp, esp_mail_smtp_cmd_greeting, esp_mail_smtp_status_code_250, SMTP_STATUS_SMTP_GREETING_SEND_ACK_FAILED))
return false;
smtp->_feature_capability[esp_mail_smtp_send_capability_esmtp] = false;
smtp->_auth_capability[esp_mail_auth_capability_login] = true;
}
else
smtp->_feature_capability[esp_mail_smtp_send_capability_esmtp] = true;
#if !defined(ESP_MAIL_DISABLE_SSL)
if (smtp->_session_cfg->int_mode != esp_mail_secure_mode_nonsecure)
{
// start TLS when needed
// rfc3207
if ((smtp->_auth_capability[esp_mail_auth_capability_starttls] || smtp->_session_cfg->int_start_tls || smtp->_session_cfg->int_mode == esp_mail_secure_mode_ssl_tls) && !ssl)
{
// send starttls command
#if !defined(SILENT_MODE)
printDebug<SMTPSession *>(smtp,
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
// expected success status code 250 for complete the request
// some server returns 220 to restart to initial state
// expected error status code 500, 501, 504, 421
smtpSend(smtp, smtp_commands[esp_mail_smtp_command_starttls].text, true);
if (!handleSMTPResponse(smtp, esp_mail_smtp_cmd_start_tls, esp_mail_smtp_status_code_250, SMTP_STATUS_SMTP_GREETING_SEND_ACK_FAILED))
return false;
#if !defined(SILENT_MODE)
if (smtp->_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 (!smtp->client.connectSSL(smtp->_session_cfg->certificate.verify))
return handleSMTPError(smtp, MAIL_CLIENT_ERROR_SSL_TLS_STRUCTURE_SETUP);
// set the secure mode
smtp->_session_cfg->int_start_tls = false;
smtp->_session_cfg->int_mode = esp_mail_secure_mode_undefined;
ssl = true;
smtp->_secure = true;
// return to initial stage if the response status is 220.
if (smtp->_responseStatus.statusCode == esp_mail_smtp_status_code_220)
goto initial_stage;
}
}
#endif
bool creds = smtp->_session_cfg->login.email.length() > 0 && smtp->_session_cfg->login.password.length() > 0;
bool sasl_auth_oauth = smtp->_session_cfg->login.accessToken.length() > 0 && smtp->_auth_capability[esp_mail_auth_capability_xoauth2];
bool sasl_login = smtp->_auth_capability[esp_mail_auth_capability_login] && creds;
bool sasl_auth_plain = smtp->_auth_capability[esp_mail_auth_capability_plain] && creds;
if (sasl_auth_oauth || sasl_login || sasl_auth_plain)
{
#if !defined(SILENT_MODE)
if (smtp->_statusCallback)
sendCB<SMTPSession *>(smtp, esp_mail_cb_str_14 /* "Logging in..." */, true, false);
#endif
// log in
if (sasl_auth_oauth)
{
#if !defined(SILENT_MODE)
if (smtp->_debug)
esp_mail_debug_print_tag(esp_mail_dbg_str_15 /* "send smtp command, AUTH XOAUTH2" */, esp_mail_debug_tag_type_client, true);
#endif
if (!smtp->_auth_capability[esp_mail_auth_capability_xoauth2])
return handleSMTPError(smtp, SMTP_STATUS_SERVER_OAUTH2_LOGIN_DISABLED, false);
MB_String cmd = smtp_cmd_post_tokens[esp_mail_smtp_command_auth];
cmd += smtp_auth_cap_post_tokens[esp_mail_auth_capability_xoauth2];
if (smtpSend(smtp, cmd.c_str(), false) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED)
return false;
if (smtpSend(smtp, getXOAUTH2String(smtp->_session_cfg->login.email, smtp->_session_cfg->login.accessToken).c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED)
return false;
if (!handleSMTPResponse(smtp, esp_mail_smtp_cmd_auth_xoauth2, esp_mail_smtp_status_code_235, SMTP_STATUS_AUTHEN_FAILED))
return false;
}
else if (sasl_auth_plain)
{
#if !defined(SILENT_MODE)
if (smtp->_debug)
{
esp_mail_debug_print_tag(esp_mail_dbg_str_7 /* "send SMTP command, AUTH PLAIN" */, esp_mail_debug_tag_type_client, true);
esp_mail_debug_print_tag(smtp->_session_cfg->login.email.c_str(), esp_mail_debug_tag_type_client, true);
MB_String mask;
maskString(mask, smtp->_session_cfg->login.password.length());
esp_mail_debug_print_tag(mask.c_str(), esp_mail_debug_tag_type_client, true);
}
#endif
// rfc4616
int len = smtp->_session_cfg->login.email.length() + smtp->_session_cfg->login.password.length() + 2;
uint8_t *tmp = allocMem<uint8_t *>(len);
memset(tmp, 0, len);
int p = 1;
memcpy(tmp + p, smtp->_session_cfg->login.email.c_str(), smtp->_session_cfg->login.email.length());
p += smtp->_session_cfg->login.email.length() + 1;
memcpy(tmp + p, smtp->_session_cfg->login.password.c_str(), smtp->_session_cfg->login.password.length());
p += smtp->_session_cfg->login.password.length();
MB_String cmd = smtp_cmd_post_tokens[esp_mail_smtp_command_auth];
cmd += smtp_cmd_post_tokens[esp_mail_smtp_command_plain];
cmd += encodeBase64Str(tmp, p);
// release memory
freeMem(&tmp);
if (smtpSend(smtp, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED)
return false;
if (!handleSMTPResponse(smtp, esp_mail_smtp_cmd_auth_plain, esp_mail_smtp_status_code_235, SMTP_STATUS_USER_LOGIN_FAILED))
return false;
}
else if (sasl_login)
{
#if !defined(SILENT_MODE)
if (smtp->_debug)
esp_mail_debug_print_tag(esp_mail_dbg_str_6 /* "send SMTP command, AUTH LOGIN" */, esp_mail_debug_tag_type_client, true);
#endif
MB_String cmd = smtp_cmd_post_tokens[esp_mail_smtp_command_auth];
cmd += smtp_commands[esp_mail_smtp_command_login].text;
if (smtpSend(smtp, cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED)
return false;
// expected server challenge response
if (!handleSMTPResponse(smtp, esp_mail_smtp_cmd_auth_login, esp_mail_smtp_status_code_334, SMTP_STATUS_AUTHEN_FAILED))
return false;
#if !defined(SILENT_MODE)
if (smtp->_debug)
esp_mail_debug_print_tag(smtp->_session_cfg->login.email.c_str(), esp_mail_debug_tag_type_client, true);
#endif
if (smtpSend(smtp, encodeBase64Str((const unsigned char *)smtp->_session_cfg->login.email.c_str(), smtp->_session_cfg->login.email.length()).c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED)
return false;
// expected server challenge response
if (!handleSMTPResponse(smtp, esp_mail_smtp_cmd_login_user, esp_mail_smtp_status_code_334, SMTP_STATUS_USER_LOGIN_FAILED))
return false;
#if !defined(SILENT_MODE)
if (smtp->_debug)
{
MB_String mask;
maskString(mask, smtp->_session_cfg->login.password.length());
esp_mail_debug_print_tag(mask.c_str(), esp_mail_debug_tag_type_client, true);
}
#endif
if (smtpSend(smtp, encodeBase64Str((const unsigned char *)smtp->_session_cfg->login.password.c_str(), smtp->_session_cfg->login.password.length()).c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED)
return false;
if (!handleSMTPResponse(smtp, esp_mail_smtp_cmd_login_psw, esp_mail_smtp_status_code_235, SMTP_STATUS_PASSWORD_LOGIN_FAILED))
return false;
}
smtp->_authenticated = true;
}
return true;
}
bool ESP_Mail_Client::addSendingResult(SMTPSession *smtp, SMTP_Message *msg, bool result, bool showResult)
{
if (!smtp)
return false;
if (result)
smtp->_sentSuccessCount++;
else
smtp->_sentFailedCount++;
if (smtp->_session_cfg->sentLogs.filename.length() > 0 && smtp->_session_cfg->sentLogs.storage_type != esp_mail_file_storage_type_none)
saveSendingLogs(smtp, msg, result);
// Store only tatest result
smtp->sendingResult.clear();
SMTP_Result status;
status.completed = result;
status.timestamp = smtp->ts;
status.subject = msg->subject.c_str();
if (msg->timestamp.tag.length() && msg->timestamp.format.length())
status.subject.replaceAll(msg->timestamp.tag, Time.getDateTimeString(Time.getCurrentTimestamp(), msg->timestamp.format.c_str()));
status.recipients = msg->_rcp[0].email.c_str();
smtp->sendingResult.add(&status);
smtp->_cbData._sentSuccess = smtp->_sentSuccessCount;
smtp->_cbData._sentFailed = smtp->_sentFailedCount;
#if !defined(SILENT_MODE)
if (smtp->_statusCallback)
{
if (showResult)
{
int bufLen = 512;
char *buf = allocMem<char *>(bufLen);
time_t ts = (time_t)smtp->ts;
MB_String sep;
for (int i = 0; i < 25; i++)
sep += '-';
sendCallback<SMTPSession *>(smtp, sep.c_str(), true, false);
snprintf(buf, bufLen, pgm2Str(esp_mail_str_93 /* "Message sent success: %d" */), smtp->_sentSuccessCount);
sendCallback<SMTPSession *>(smtp, buf, false, false);
snprintf(buf, bufLen, pgm2Str(esp_mail_str_94 /* "Message sent failed: %d" */), smtp->_sentFailedCount);
sendCallback<SMTPSession *>(smtp, buf, false, false);
sendCallback<SMTPSession *>(smtp, sep.c_str(), false, false);
snprintf(buf, bufLen, pgm2Str(esp_mail_str_95 /* "Status: %s" */), result ? pgm2Str(esp_mail_str_98 /* "success" */) : pgm2Str(esp_mail_str_99 /* "failed" */));
sendCallback<SMTPSession *>(smtp, buf, false, false);
snprintf(buf, bufLen, pgm2Str(esp_mail_str_96 /* "Date/Time: %s" */), Time.getDateTimeString(ts, "%B %d, %Y %H:%M:%S").c_str());
sendCallback<SMTPSession *>(smtp, buf, false, false);
snprintf(buf, bufLen, pgm2Str(esp_mail_str_97 /* "Recipient: %s" */), msg->_rcp[0].email.c_str());
sendCallback<SMTPSession *>(smtp, buf, false, false);
snprintf(buf, bufLen, pgm2Str(esp_mail_str_92 /* "Subject: %s" */), msg->subject.c_str());
sendCallback<SMTPSession *>(smtp, buf, false, false);
freeMem(&buf);
}
}
#endif
return result;
}
void ESP_Mail_Client::saveSendingLogs(SMTPSession *smtp, SMTP_Message *msg, bool result)
{
if (!smtp->_session_cfg || smtp->_session_cfg->sentLogs.filename.length() == 0 || smtp->_session_cfg->sentLogs.storage_type == esp_mail_file_storage_type_none)
return;
int sz = mbfs->open(smtp->_session_cfg->sentLogs.filename.c_str(), mbfs_type smtp->_session_cfg->sentLogs.storage_type, mb_fs_open_mode_append);
if (sz < 0)
return;
MB_String cm = esp_mail_str_8; /* "," */
mbfs->print(mbfs_type smtp->_session_cfg->sentLogs.storage_type, (int)result);
mbfs->print(mbfs_type smtp->_session_cfg->sentLogs.storage_type, cm.c_str());
mbfs->print(mbfs_type smtp->_session_cfg->sentLogs.storage_type, (int)smtp->ts);
mbfs->print(mbfs_type smtp->_session_cfg->sentLogs.storage_type, cm.c_str());
mbfs->print(mbfs_type smtp->_session_cfg->sentLogs.storage_type, msg->_rcp[0].email.c_str());
mbfs->print(mbfs_type smtp->_session_cfg->sentLogs.storage_type, cm.c_str());
mbfs->println(mbfs_type smtp->_session_cfg->sentLogs.storage_type, msg->subject.c_str());
mbfs->close(mbfs_type smtp->_session_cfg->sentLogs.storage_type);
}
bool ESP_Mail_Client::sendMail(SMTPSession *smtp, SMTP_Message *msg, bool closeSession)
{
if (!smtp || !sessionExisted<SMTPSession *>(smtp))
return false;
smtp->_customCmdResCallback = NULL;
return mSendMail(smtp, msg, closeSession);
}
size_t ESP_Mail_Client::numAtt(SMTPSession *smtp, esp_mail_attach_type type, SMTP_Message *msg)
{
size_t count = 0;
for (size_t i = 0; i < msg->_att.size(); i++)
{
if (msg->_att[i]._int.att_type == type)
count++;
}
return count;
}
bool ESP_Mail_Client::checkEmail(SMTPSession *smtp, SMTP_Message *msg)
{
bool validRecipient = false;
if (!validEmail(msg->sender.email.c_str()))
{
errorStatusCB<SMTPSession *, IMAPSession *>(smtp, this->imap, SMTP_STATUS_NO_VALID_SENDER_EXISTED, false);
return addSendingResult(smtp, msg, false, true);
}
for (uint8_t i = 0; i < msg->_rcp.size(); i++)
{
if (validEmail(msg->_rcp[i].email.c_str()))
validRecipient = true;
}
if (!validRecipient)
{
errorStatusCB<SMTPSession *, IMAPSession *>(smtp, this->imap, SMTP_STATUS_NO_VALID_RECIPIENTS_EXISTED, false);
return addSendingResult(smtp, msg, false, true);
}
return true;
}
bool ESP_Mail_Client::mSendMail(SMTPSession *smtp, SMTP_Message *msg, bool closeSession)
{
if (!smtp)
return false;
smtp->_responseStatus.errorCode = 0;
smtp->_responseStatus.statusCode = 0;
smtp->_responseStatus.text.clear();
smtp->_cbData._success = false;
bool rfc822MSG = false;
if (!checkEmail(smtp, msg))
return false;
smtp->_chunkedEnable = false;
smtp->_chunkCount = 0;
if (!smtp->connected() && !smtp->_loginStatus)
{
#if !defined(SILENT_MODE)
if (smtp->_debug && smtp->_statusCallback && !smtp->_customCmdResCallback)
{
esp_mail_debug_print();
errorStatusCB<SMTPSession *, IMAPSession *>(smtp, this->imap, MAIL_CLIENT_ERROR_NOT_YET_LOGIN, false);
}
#endif
return false;
}
// new session
if (!smtp->connected())
{
bool ssl = false;
if (!smtp->connect(ssl))
{
closeTCPSession<SMTPSession *>(smtp);
return addSendingResult(smtp, msg, false, true);
}
if (!smtpAuth(smtp, ssl))
{
closeTCPSession<SMTPSession *>(smtp);
return addSendingResult(smtp, msg, false, true);
}
smtp->_sentSuccessCount = 0;
smtp->_sentFailedCount = 0;
smtp->sendingResult.clear();
}
else
{
// If time config changed, we will update time
MailClient.prepareTime<SMTPSession *>(smtp->_session_cfg, smtp);
// reuse session
#if !defined(SILENT_MODE)
PGM_P p1 = smtp->_sentSuccessCount || smtp->_sentFailedCount ? esp_mail_cb_str_10 /* "Sending next Email..." */ : esp_mail_cb_str_9 /* "Sending Email..." */;
PGM_P p2 = smtp->_sentSuccessCount || smtp->_sentFailedCount ? esp_mail_dbg_str_13 /* "send next Email" */ : esp_mail_dbg_str_3 /* "send Email" */;
printDebug<SMTPSession *>(smtp,
p1,
p2,
esp_mail_debug_tag_type_client,
true,
false);
#endif
}
#if !defined(SILENT_MODE)
printDebug<SMTPSession *>(smtp,
esp_mail_cb_str_4 /* "Sending message header..." */,
esp_mail_dbg_str_8 /* "send message header" */,
esp_mail_debug_tag_type_client,
true,
false);
#endif
imap = nullptr;
calDataLen = false;
dataLen = 0;
return sendContent(smtp, msg, closeSession, rfc822MSG);
}
bool ESP_Mail_Client::sendContent(SMTPSession *smtp, SMTP_Message *msg, bool closeSession, bool rfc822MSG)
{
if (msg->html.content.length() > 0 || strlen(msg->html.nonCopyContent) > 0 || msg->html.blob.size > 0 || msg->html.file.name.length() > 0)
msg->type |= esp_mail_msg_type_html;
if (msg->text.content.length() > 0 || strlen(msg->text.nonCopyContent) > 0 || msg->text.blob.size > 0 || msg->text.file.name.length() > 0)
msg->type |= esp_mail_msg_type_plain;
for (size_t i = 0; i < msg->_rfc822.size(); i++)
{
if (msg->_rfc822[i].html.content.length() > 0)
msg->_rfc822[i].type |= esp_mail_msg_type_html;
if (msg->_rfc822[i].text.content.length() > 0)
msg->_rfc822[i].type |= esp_mail_msg_type_plain;
}
MB_String buf;
MB_String buf2;
checkUnencodedData(smtp, msg);
if (msg->priority >= esp_mail_smtp_priority_high && msg->priority <= esp_mail_smtp_priority_low)
{
appendHeaderField(buf2, message_headers[esp_mail_message_header_field_x_priority].text, MB_String(msg->priority).c_str(), false, true);
PGM_P p = nullptr;
if (msg->priority == esp_mail_smtp_priority_high)
p = esp_mail_str_4; /* "High" */
else if (msg->priority == esp_mail_smtp_priority_normal)
p = esp_mail_str_5; /* "Normal" */
else if (msg->priority == esp_mail_smtp_priority_low)
p = esp_mail_str_6; /* "Low" */
if (p)
{
appendHeaderField(buf2, message_headers[esp_mail_message_header_field_x_msmail_priority].text, p, false, true);
appendHeaderField(buf2, message_headers[esp_mail_message_header_field_importance].text, p, false, true);
}
}
// If author and transmitter (sender or agent) are not identical, send both 'From' and 'Sender' headers
if (msg->sender.email.length() > 0 && msg->author.email.length() > 0 &&
strcmp(msg->sender.email.c_str(), msg->author.email.c_str()) != 0)
{
appendAddressHeaderField(buf2, msg->author, esp_mail_rfc822_header_field_from, true, false, true);
appendAddressHeaderField(buf2, msg->sender, esp_mail_rfc822_header_field_sender, true, false, true);
}
// If author and transmitter (agent) are identical, send only 'From' header
else if (msg->sender.email.length() > 0)
appendAddressHeaderField(buf2, msg->sender, esp_mail_rfc822_header_field_from, true, false, true);
if (!imap && smtp)
{
buf = smtp_cmd_post_tokens[esp_mail_smtp_command_mail];
buf += smtp_commands[esp_mail_smtp_command_from].text;
appendString(buf, msg->author.email.length() ? msg->author.email.c_str() : msg->sender.email.c_str(), false, false, esp_mail_string_mark_type_angle_bracket);
if (msg->text._int.xencoding == esp_mail_msg_xencoding_binary || msg->html._int.xencoding == esp_mail_msg_xencoding_binary)
{
if (smtp->_feature_capability[esp_mail_smtp_send_capability_binary_mime] || (smtp->_feature_capability[esp_mail_smtp_send_capability_chunking] && msg->enable.chunking))
{
buf += smtp_cmd_pre_tokens[esp_mail_smtp_command_body];
buf += esp_mail_str_7; /* "=" */
buf += smtp_send_capabilities[esp_mail_smtp_send_capability_binary_mime].text;
}
}
else if (msg->text._int.xencoding == esp_mail_msg_xencoding_8bit || msg->html._int.xencoding == esp_mail_msg_xencoding_8bit)
{
if (smtp->_feature_capability[esp_mail_smtp_send_capability_8bit_mime])
{
buf += smtp_cmd_pre_tokens[esp_mail_smtp_command_body];
buf += esp_mail_str_7; /* "=" */
buf += smtp_send_capabilities[esp_mail_smtp_send_capability_8bit_mime].text;
}
}
// expected success status code 250
// expected failure status code 552, 451, 452
// expected error status code 500, 501, 421
if (!altSendData(buf, true, smtp, msg, true, true, esp_mail_smtp_cmd_send_header_sender, esp_mail_smtp_status_code_250, SMTP_STATUS_SEND_HEADER_SENDER_FAILED))
return false;
}
// Construct 'To' header fields.
for (uint8_t i = 0; i < msg->_rcp.size(); i++)
{
appendAddressHeaderField(buf2, msg->_rcp[i], esp_mail_rfc822_header_field_to, i == 0, i > 0, i == msg->_rcp.size() - 1);
if (!imap && smtp)
{
// only address
buf = smtp_cmd_post_tokens[esp_mail_smtp_command_rcpt];
buf += smtp_commands[esp_mail_smtp_command_to].text;
appendString(buf, msg->_rcp[i].email.c_str(), false, false, esp_mail_string_mark_type_angle_bracket);
// rfc3461, rfc3464
if (smtp->_feature_capability[esp_mail_smtp_send_capability_dsn])
{
if (msg->response.notify != esp_mail_smtp_notify_never)
{
buf += smtp_cmd_pre_tokens[esp_mail_smtp_command_notify];
buf += esp_mail_str_7; /* "=" */
MB_String notify;
if ((msg->response.notify & esp_mail_smtp_notify_success) == esp_mail_smtp_notify_success)
notify = smtp_commands[esp_mail_smtp_command_success].text;
if (notify.length())
notify += esp_mail_str_8; /* "," */
if ((msg->response.notify & esp_mail_smtp_notify_failure) == esp_mail_smtp_notify_failure)
notify += smtp_commands[esp_mail_smtp_command_failure].text;
if (notify.length())
notify += esp_mail_str_8; /* "," */
if ((msg->response.notify & esp_mail_smtp_notify_delay) == esp_mail_smtp_notify_delay)
notify += smtp_commands[esp_mail_smtp_command_delay].text;
buf += notify;
}
}
smtp->_canForward = true;
// expected success status code 250, 251
// expected failure status code 550, 551, 552, 553, 450, 451, 452
// expected error status code 500, 501, 503, 421
if (!altSendData(buf, true, smtp, msg, true, true, esp_mail_smtp_cmd_send_header_recipient, esp_mail_smtp_status_code_250, SMTP_STATUS_SEND_HEADER_RECIPIENT_FAILED))
return false;
}
}
// Construct 'Cc' header field.
for (uint8_t i = 0; i < msg->_cc.size(); i++)
{
appendAddressHeaderField(buf2, msg->_cc[i], esp_mail_rfc822_header_field_cc, i == 0, i > 0, i == msg->_cc.size() - 1);
if (!imap)
{
// only address
buf = smtp_cmd_post_tokens[esp_mail_smtp_command_rcpt];
buf += smtp_commands[esp_mail_smtp_command_to].text;
appendString(buf, msg->_cc[i].email.c_str(), false, false, esp_mail_string_mark_type_angle_bracket);
smtp->_canForward = true;
// expected success status code 250, 251
// expected failure status code 550, 551, 552, 553, 450, 451, 452
// expected error status code 500, 501, 503, 421
if (!altSendData(buf, true, smtp, msg, true, true, esp_mail_smtp_cmd_send_header_recipient, esp_mail_smtp_status_code_250, SMTP_STATUS_SEND_HEADER_RECIPIENT_FAILED))
return false;
}
}
if (!imap && smtp)
{
for (uint8_t i = 0; i < msg->_bcc.size(); i++)
{
// only address
buf = smtp_cmd_post_tokens[esp_mail_smtp_command_rcpt];
buf += smtp_commands[esp_mail_smtp_command_to].text;
appendString(buf, msg->_bcc[i].email.c_str(), false, false, esp_mail_string_mark_type_angle_bracket);
smtp->_canForward = true;
// expected success status code 250, 251
// expected failure status code 550, 551, 552, 553, 450, 451, 452
// expected error status code 500, 501, 503, 421
if (!altSendData(buf, true, smtp, msg, true, true, esp_mail_smtp_cmd_send_header_recipient, esp_mail_smtp_status_code_250, SMTP_STATUS_SEND_HEADER_RECIPIENT_FAILED))
return false;
}
#if !defined(SILENT_MODE)
altSendCallback(smtp, esp_mail_cb_str_5 /* "Sending message body..." */, esp_mail_dbg_str_9 /* "send message body" */, esp_mail_debug_tag_type_client, true, false);
#endif
if (smtp->_feature_capability[esp_mail_smtp_send_capability_chunking] && msg->enable.chunking)
{
smtp->_chunkedEnable = true;
if (!sendBDAT(smtp, msg, buf2.length(), false))
return false;
}
else
{
// expected success status code 354
// expected failure status code 451, 554
// expected error status code 500, 501, 503, 421
MB_String sdata = smtp_commands[esp_mail_smtp_command_data].text;
if (!altSendData(sdata, true, smtp, msg, true, true, esp_mail_smtp_cmd_send_body, esp_mail_smtp_status_code_354, SMTP_STATUS_SEND_BODY_FAILED))
return false;
}
}
#if defined(ENABLE_IMAP) && !defined(SILENT_MODE)
if (imap)
altSendCallback(smtp, esp_mail_cb_str_14 /* "Appending message..." */, esp_mail_dbg_str_69 /* "appending message" */, esp_mail_debug_tag_type_client, true, false);
#endif
if (!altSendData(buf2, false, smtp, msg, true, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
return false;
MB_String s;
appendHeaderField(s, rfc822_headers[esp_mail_rfc822_header_field_subject].text, msg->subject.c_str(), false, true);
if (msg->timestamp.tag.length() && msg->timestamp.format.length())
s.replaceAll(msg->timestamp.tag, Time.getDateTimeString(Time.getCurrentTimestamp(), msg->timestamp.format.c_str()));
// Construct the 'Date' header field.
// The 'Date' header field should be valid and should be included in the message headers to
// prevent the 'spam' or 'junk' message considered by mail server.
bool dateHdr = false;
MB_String dt;
uint32_t ts = 0;
if (smtp)
smtp->ts = ts;
// Check if valid 'Date' field assigned from custom headers.
if (msg->_hdr.size() > 0)
{
for (uint8_t k = 0; k < msg->_hdr.size(); k++)
{
appendString(s, msg->_hdr[k].c_str(), false, true);
if (getHeader(msg->_hdr[k].c_str(), rfc822_headers[esp_mail_rfc822_header_field_date].text, dt, false))
{
ts = Time.getTimestamp(dt.c_str(), true);
dateHdr = ts > ESP_MAIL_CLIENT_VALID_TS;
if (smtp)
smtp->ts = ts;
}
}
}
// Check if valid 'Date' field assigned from SMTP_Message's date property.
if (!dateHdr && msg->date.length() > 0)
{
dt = msg->date;
ts = Time.getTimestamp(msg->date.c_str(), true);
dateHdr = ts > ESP_MAIL_CLIENT_VALID_TS;
if (smtp)
smtp->ts = ts;
}
if (dateHdr)
{
// 'Date' header field assigned.
appendHeaderField(s, rfc822_headers[esp_mail_rfc822_header_field_date].text, dt.c_str(), false, true);
}
else
{
// If there is no 'Date' field assigned, get time from system and construct 'Date' header field.
if (smtp)
{
ts = MailClient.Time.getCurrentTimestamp();
smtp->ts = ts;
}
else if (imap)
{
#if defined(ENABLE_IMAP)
if (calDataLen)
{
ts = MailClient.Time.getCurrentTimestamp();
imap_ts = ts;
}
else
ts = imap_ts;
#endif
}
if (ts > ESP_MAIL_CLIENT_VALID_TS)
appendHeaderField(s, rfc822_headers[esp_mail_rfc822_header_field_date].text, Time.getDateTimeString().c_str(), false, true);
}
if (msg->response.reply_to.length() > 0)
appendHeaderField(s, rfc822_headers[esp_mail_rfc822_header_field_reply_to].text, msg->response.reply_to.c_str(), false, true, esp_mail_string_mark_type_angle_bracket);
if (msg->response.return_path.length() > 0)
appendHeaderField(s, rfc822_headers[esp_mail_rfc822_header_field_return_path].text, msg->response.return_path.c_str(), false, true, esp_mail_string_mark_type_angle_bracket);
if (msg->in_reply_to.length() > 0)
appendHeaderField(s, rfc822_headers[esp_mail_rfc822_header_field_in_reply_to].text, msg->in_reply_to.c_str(), false, true);
if (msg->references.length() > 0)
appendHeaderField(s, rfc822_headers[esp_mail_rfc822_header_field_references].text, msg->references.c_str(), false, true);
if (msg->comments.length() > 0)
appendHeaderField(s, rfc822_headers[esp_mail_rfc822_header_field_comments].text, msg->comments.c_str(), false, true);
if (msg->keywords.length() > 0)
appendHeaderField(s, rfc822_headers[esp_mail_rfc822_header_field_keywords].text, msg->keywords.c_str(), false, true);
if (msg->messageID.length() > 0)
appendHeaderField(s, rfc822_headers[esp_mail_rfc822_header_field_msg_id].text, msg->messageID.c_str(), false, true, esp_mail_string_mark_type_angle_bracket);
appendHeaderField(s, message_headers[esp_mail_message_header_field_mime_version].text, esp_mail_str_51 /* "1.0" */, false, true);
if (!sendBDAT(smtp, msg, s.length(), false))
return false;
if (!altSendData(s, false, smtp, msg, true, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
return false;
return sendMSGData(smtp, msg, closeSession, rfc822MSG);
}
void ESP_Mail_Client::altSendCallback(SMTPSession *smtp, PGM_P cbMsg, PGM_P dbMsg, esp_mail_debug_tag_type type, bool prependCRLF, bool success)
{
#if !defined(SILENT_MODE)
if (smtp)
printDebug<SMTPSession *>(smtp, cbMsg, dbMsg, type, prependCRLF, success);
else if (imap && !calDataLen)
{
#if defined(ENABLE_IMAP)
printDebug<IMAPSession *>(imap, cbMsg, dbMsg, type, prependCRLF, success);
#endif
}
#endif
}
bool ESP_Mail_Client::sendMSGData(SMTPSession *smtp, SMTP_Message *msg, bool closeSession, bool rfc822MSG)
{
MB_String s;
MB_String mixed = getMIMEBoundary(15);
MB_String alt = getMIMEBoundary(15);
if (numAtt(smtp, esp_mail_att_type_attachment, msg) == 0 && msg->_parallel.size() == 0 && msg->_rfc822.size() == 0)
{
if (msg->type == (esp_mail_msg_type_plain | esp_mail_msg_type_html | esp_mail_msg_type_enriched) || numAtt(smtp, esp_mail_att_type_inline, msg) > 0)
{
if (!sendMSG(smtp, msg, alt))
return addSendingResult(smtp, msg, false, true);
}
else if (msg->type != esp_mail_msg_type_none)
{
if (!sendPartText(smtp, msg, msg->type, ""))
return addSendingResult(smtp, msg, false, true);
}
}
else
{
s.clear();
appendMultipartContentType(s, esp_mail_multipart_type_mixed, mixed.c_str());
appendBoundaryString(s, mixed.c_str(), false, true);
if (!sendBDAT(smtp, msg, s.length(), false))
return false;
if (!altSendData(s, false, smtp, msg, true, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
return false;
if (!sendMSG(smtp, msg, alt))
return addSendingResult(smtp, msg, false, true);
if (!sendBDAT(smtp, msg, 2, false))
return false;
MB_String str;
appendNewline(str);
if (!altSendData(str, false, smtp, msg, true, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
return false;
#if !defined(SILENT_MODE)
altSendCallback(smtp, esp_mail_cb_str_6 /* "Sending attachments..." */, esp_mail_dbg_str_10 /* "send attachments" */, esp_mail_debug_tag_type_client, true, false);
#endif
if (!sendAttachments(smtp, msg, mixed))
return addSendingResult(smtp, msg, false, true);
if (!sendParallelAttachments(smtp, msg, mixed))
return addSendingResult(smtp, msg, false, true);
if (!sendRFC822Msg(smtp, msg, mixed, closeSession, msg->_rfc822.size() > 0))
return addSendingResult(smtp, msg, false, true);
s.clear();
appendBoundaryString(s, mixed.c_str(), true, false);
if (!sendBDAT(smtp, msg, s.length(), false))
return false;
if (!altSendData(s, false, smtp, msg, true, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
return false;
}
if (!rfc822MSG && !imap && smtp)
{
#if !defined(SILENT_MODE)
altSendCallback(smtp, esp_mail_cb_str_11 /* "Finishing the message sending..." */, esp_mail_dbg_str_16 /* "finishing the message sending" */, esp_mail_debug_tag_type_client, true, false);
#endif
if (smtp->_chunkedEnable)
{
if (!sendBDAT(smtp, msg, 0, true))
return false;
// expected success status code 250
// expected failure status code 451, 554
// expected error status code 500, 501, 503, 421
if (!handleSMTPResponse(smtp, esp_mail_smtp_cmd_chunk_termination, esp_mail_smtp_status_code_250, SMTP_STATUS_SEND_BODY_FAILED))
return false;
}
else
{
// expected success status code 250
// expected failure status code 451, 554
// expected error status code 500, 501, 503, 421
MB_String str = smtp_commands[esp_mail_smtp_command_terminate].text;
if (!altSendData(str, false, smtp, msg, true, true, esp_mail_smtp_cmd_send_body, esp_mail_smtp_status_code_250, SMTP_STATUS_SEND_BODY_FAILED))
return false;
}
addSendingResult(smtp, msg, true, !closeSession);
if (closeSession && smtp)
{
if (!smtp->closeSession())
return false;
}
}
if (smtp)
smtp->_cbData._success = true;
return true;
}
bool ESP_Mail_Client::sendRFC822Msg(SMTPSession *smtp, SMTP_Message *msg, const MB_String &boundary, bool closeSession, bool rfc822MSG)
{
if (msg->_rfc822.size() == 0)
return true;
MB_String buf;
for (uint8_t i = 0; i < msg->_rfc822.size(); i++)
{
buf.clear();
getRFC822PartHeader(smtp, buf, boundary);
getRFC822MsgEnvelope(smtp, &msg->_rfc822[i], buf);
if (!sendBDAT(smtp, msg, buf.length(), false))
return false;
if (!altSendData(buf, false, smtp, msg, false, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
return false;
if (!sendMSGData(smtp, &msg->_rfc822[i], closeSession, rfc822MSG))
return false;
}
return true;
}
void ESP_Mail_Client::getRFC822MsgEnvelope(SMTPSession *smtp, SMTP_Message *msg, MB_String &buf)
{
if (msg->date.length() > 0)
appendHeaderField(buf, rfc822_headers[esp_mail_rfc822_header_field_date].text, msg->date.c_str(), false, true);
else
{
time_t now = 0;
if (smtp)
now = MailClient.Time.getCurrentTimestamp();
else if (imap)
{
#if defined(ENABLE_IMAP)
if (calDataLen)
{
now = MailClient.Time.getCurrentTimestamp();
imap_ts = now;
}
else
now = imap_ts;
#endif
}
if (now > ESP_MAIL_CLIENT_VALID_TS)
appendHeaderField(buf, rfc822_headers[esp_mail_rfc822_header_field_date].text, Time.getDateTimeString().c_str(), false, true);
}
// Construct 'From' header field.
if (msg->from.email.length() > 0)
appendAddressHeaderField(buf, msg->from, esp_mail_rfc822_header_field_from, true, false, true);
// Construct 'Sender' header field.
if (msg->sender.email.length() > 0)
appendAddressHeaderField(buf, msg->sender, esp_mail_rfc822_header_field_sender, true, false, true);
if (msg->response.reply_to.length() > 0)
appendHeaderField(buf, rfc822_headers[esp_mail_rfc822_header_field_reply_to].text, msg->response.reply_to.c_str(), false, true, esp_mail_string_mark_type_angle_bracket);
if (msg->response.return_path.length() > 0)
appendHeaderField(buf, rfc822_headers[esp_mail_rfc822_header_field_return_path].text, msg->response.return_path.c_str(), false, true, esp_mail_string_mark_type_angle_bracket);
// Construct 'To' header field.
for (uint8_t i = 0; i < msg->_rcp.size(); i++)
appendAddressHeaderField(buf, msg->_rcp[i], esp_mail_rfc822_header_field_to, i == 0, i > 0, i == msg->_rcp.size() - 1);
for (uint8_t i = 0; i < msg->_cc.size(); i++)
appendAddressHeaderField(buf, msg->_cc[i], esp_mail_rfc822_header_field_cc, i == 0, i > 0, i == msg->_cc.size() - 1);
for (uint8_t i = 0; i < msg->_bcc.size(); i++)
appendAddressHeaderField(buf, msg->_bcc[i], esp_mail_rfc822_header_field_bcc, i == 0, i > 0, i == msg->_bcc.size() - 1);
if (msg->subject.length() > 0)
appendHeaderField(buf, rfc822_headers[esp_mail_rfc822_header_field_subject].text, msg->subject.c_str(), false, true);
if (msg->keywords.length() > 0)
appendHeaderField(buf, rfc822_headers[esp_mail_rfc822_header_field_keywords].text, msg->keywords.c_str(), false, true);
if (msg->comments.length() > 0)
appendHeaderField(buf, rfc822_headers[esp_mail_rfc822_header_field_comments].text, msg->comments.c_str(), false, true);
if (msg->in_reply_to.length() > 0)
appendHeaderField(buf, rfc822_headers[esp_mail_rfc822_header_field_in_reply_to].text, msg->in_reply_to.c_str(), false, true);
if (msg->references.length() > 0)
appendHeaderField(buf, rfc822_headers[esp_mail_rfc822_header_field_references].text, msg->references.c_str(), false, true);
if (msg->messageID.length() > 0)
appendHeaderField(buf, rfc822_headers[esp_mail_rfc822_header_field_msg_id].text, msg->messageID.c_str(), false, true, esp_mail_string_mark_type_angle_bracket);
}
void ESP_Mail_Client::appendBoundaryString(MB_String &buf, const char *value, bool endMark, bool newLine)
{
buf += esp_mail_str_9; /* "--" */
buf += value;
if (endMark)
buf += esp_mail_str_9; /* "--" */
if (newLine)
appendNewline(buf);
}
bool ESP_Mail_Client::sendBDAT(SMTPSession *smtp, SMTP_Message *msg, int len, bool last)
{
if (!smtp)
return true;
if (!smtp->_chunkedEnable || !msg->enable.chunking)
return true;
smtp->_chunkCount++;
MB_String bdat = smtp_cmd_post_tokens[esp_mail_smtp_command_bdat];
bdat += len;
if (last)
bdat += smtp_cmd_pre_tokens[esp_mail_smtp_command_last];
if (smtpSend(smtp, bdat.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED)
return addSendingResult(smtp, msg, false, true);
if (!smtp->_feature_capability[esp_mail_smtp_send_capability_pipelining])
{
// expected success status code 250
// expected failure status code 451, 554
// expected error status code 500, 501, 503, 421
if (!handleSMTPResponse(smtp, esp_mail_smtp_cmd_send_body, esp_mail_smtp_status_code_250, SMTP_STATUS_SEND_BODY_FAILED))
return addSendingResult(smtp, msg, false, true);
smtp->_chunkCount = 0;
}
return true;
}
void ESP_Mail_Client::getXEncoding(esp_mail_msg_xencoding &xencoding, const char *enc)
{
if (strcmp(enc, Content_Transfer_Encoding::enc_binary) == 0)
xencoding = esp_mail_msg_xencoding_binary;
else if (strcmp(enc, Content_Transfer_Encoding::enc_8bit) == 0)
xencoding = esp_mail_msg_xencoding_8bit;
else if (strcmp(enc, Content_Transfer_Encoding::enc_7bit) == 0)
xencoding = esp_mail_msg_xencoding_7bit;
}
void ESP_Mail_Client::checkUnencodedData(SMTPSession *smtp, SMTP_Message *msg)
{
if (msg->type & esp_mail_msg_type_plain || msg->type == esp_mail_msg_type_enriched || msg->type & esp_mail_msg_type_html)
{
if ((msg->type & esp_mail_msg_type_plain || msg->type == esp_mail_msg_type_enriched) > 0 && msg->text.transfer_encoding.length() > 0)
getXEncoding(msg->text._int.xencoding, msg->text.transfer_encoding.c_str());
if ((msg->type & esp_mail_msg_type_html) > 0 && msg->html.transfer_encoding.length() > 0)
getXEncoding(msg->html._int.xencoding, msg->html.transfer_encoding.c_str());
}
for (size_t i = 0; i < msg->_att.size(); i++)
getXEncoding(msg->_att[i]._int.xencoding, msg->_att[i].descr.transfer_encoding.c_str());
}
bool ESP_Mail_Client::altIsCB(SMTPSession *smtp)
{
bool cb = false;
if (smtp)
cb = smtp->_statusCallback != NULL;
else if (imap && !calDataLen)
{
#if defined(ENABLE_IMAP)
cb = imap->_statusCallback != NULL;
#endif
}
return cb;
}
bool ESP_Mail_Client::altIsDebug(SMTPSession *smtp)
{
bool dbg = false;
if (smtp)
dbg = smtp->_debug;
else if (imap && !calDataLen)
{
#if defined(ENABLE_IMAP)
dbg = imap->_debug;
#endif
}
return dbg;
}
bool ESP_Mail_Client::sendBlobAttachment(SMTPSession *smtp, SMTP_Message *msg, SMTP_Attachment *att)
{
bool cb = altIsCB(smtp);
uint32_t addr = altProgressPtr(smtp);
if (strcmp(att->descr.transfer_encoding.c_str(), Content_Transfer_Encoding::enc_base64) == 0 && strcmp(att->descr.transfer_encoding.c_str(), att->descr.content_encoding.c_str()) != 0)
{
esp_mail_smtp_send_base64_data_info_t data_info;
data_info.rawPtr = att->blob.data;
data_info.size = att->blob.size;
data_info.flashMem = att->_int.flash_blob;
data_info.filename = att->descr.filename.c_str();
if (!sendBase64(smtp, msg, data_info, true, cb))
return false;
return true;
}
else
{
if (att->blob.size > 0)
{
if (strcmp(att->descr.content_encoding.c_str(), Content_Transfer_Encoding::enc_base64) == 0)
{
esp_mail_smtp_send_base64_data_info_t data_info;
data_info.rawPtr = att->blob.data;
data_info.size = att->blob.size;
data_info.flashMem = att->_int.flash_blob;
data_info.filename = att->descr.filename.c_str();
if (!sendBase64(smtp, msg, data_info, false, cb))
return false;
return true;
}
else
{
size_t chunkSize = ESP_MAIL_CLIENT_STREAM_CHUNK_SIZE;
size_t writeLen = 0;
uint8_t *buf = allocMem<uint8_t *>(chunkSize);
while (writeLen < att->blob.size)
{
if (writeLen > att->blob.size - chunkSize)
chunkSize = att->blob.size - writeLen;
if (!sendBDAT(smtp, msg, chunkSize, false))
break;
memcpy_P(buf, att->blob.data, chunkSize);
if (!altSendData(buf, chunkSize, smtp, msg, false, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
break;
if (smtp->_debug)
uploadReport(att->descr.filename.c_str(), addr, 100 * writeLen / att->blob.size);
writeLen += chunkSize;
}
// release memory
freeMem(&buf);
if (smtp->_debug)
uploadReport(att->descr.filename.c_str(), addr, 100);
return writeLen >= att->blob.size;
}
}
}
return false;
}
bool ESP_Mail_Client::sendFile(SMTPSession *smtp, SMTP_Message *msg, SMTP_Attachment *att)
{
bool cb = altIsCB(smtp);
uint32_t addr = altProgressPtr(smtp);
if (strcmp(att->descr.transfer_encoding.c_str(), Content_Transfer_Encoding::enc_base64) == 0 && strcmp(att->descr.transfer_encoding.c_str(), att->descr.content_encoding.c_str()) != 0)
{
esp_mail_smtp_send_base64_data_info_t data_info;
data_info.filename = att->descr.filename.c_str();
data_info.storageType = att->file.storage_type;
if (!sendBase64(smtp, msg, data_info, true, cb))
return false;
return true;
}
else
{
if (mbfs->size(mbfs_type att->file.storage_type) > 0)
{
if (strcmp(att->descr.content_encoding.c_str(), Content_Transfer_Encoding::enc_base64) == 0)
{
esp_mail_smtp_send_base64_data_info_t data_info;
data_info.filename = att->descr.filename.c_str();
data_info.storageType = att->file.storage_type;
if (!sendBase64(smtp, msg, data_info, false, cb))
return false;
return true;
}
else
{
int chunkSize = ESP_MAIL_CLIENT_STREAM_CHUNK_SIZE;
int writeLen = 0;
int fileSize = mbfs->size(mbfs_type att->file.storage_type);
if (fileSize < chunkSize)
chunkSize = fileSize;
uint8_t *buf = allocMem<uint8_t *>(chunkSize);
while (writeLen < fileSize && mbfs->available(mbfs_type att->file.storage_type))
{
if (writeLen > fileSize - chunkSize)
chunkSize = fileSize - writeLen;
int readLen = mbfs->read(mbfs_type att->file.storage_type, buf, chunkSize);
if (readLen != chunkSize)
{
errorStatusCB<SMTPSession *, IMAPSession *>(smtp, this->imap, MB_FS_ERROR_FILE_IO_ERROR, false);
break;
}
if (!sendBDAT(smtp, msg, chunkSize, false))
break;
if (!altSendData(buf, chunkSize, smtp, msg, false, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
break;
if (smtp->_debug)
uploadReport(att->descr.filename.c_str(), addr, 100 * writeLen / fileSize);
writeLen += chunkSize;
}
// release memory
freeMem(&buf);
if (smtp->_debug)
uploadReport(att->descr.filename.c_str(), addr, 100);
return writeLen == fileSize;
}
}
return false;
}
return false;
}
bool ESP_Mail_Client::sendParallelAttachments(SMTPSession *smtp, SMTP_Message *msg, const MB_String &boundary)
{
if (msg->_parallel.size() == 0)
return true;
MB_String parallel = getMIMEBoundary(15);
MB_String buf;
appendBoundaryString(buf, boundary.c_str(), false, true);
appendMultipartContentType(buf, esp_mail_multipart_type_parallel, parallel.c_str());
if (!sendBDAT(smtp, msg, buf.length(), false))
return false;
if (!altSendData(buf, false, smtp, msg, true, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
return false;
if (!sendAttachments(smtp, msg, parallel, true))
return addSendingResult(smtp, msg, false, true);
buf.clear();
appendBoundaryString(buf, parallel.c_str(), true, false);
if (!sendBDAT(smtp, msg, buf.length(), false))
return false;
if (!altSendData(buf, false, smtp, msg, true, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
return false;
return true;
}
bool ESP_Mail_Client::sendAttachments(SMTPSession *smtp, SMTP_Message *msg, const MB_String &boundary, bool parallel)
{
MB_String s;
MB_String buf;
int cnt = 0;
SMTP_Attachment *att = nullptr;
size_t sz = msg->_att.size();
if (parallel)
sz = msg->_parallel.size();
for (size_t i = 0; i < sz; i++)
{
if (parallel)
att = &msg->_parallel[i];
else
att = &msg->_att[i];
if (att->_int.att_type == esp_mail_att_type_attachment)
{
#if !defined(SILENT_MODE)
altSendCallback(smtp, att->descr.filename.c_str(), att->descr.filename.c_str(), esp_mail_debug_tag_type_client, true, false);
#endif
cnt++;
if (att->file.storage_type == esp_mail_file_storage_type_none)
{
if (!att->blob.data)
continue;
if (att->blob.size == 0)
continue;
buf.clear();
getAttachHeader(buf, boundary, att, att->blob.size, false);
if (!sendBDAT(smtp, msg, buf.length(), false))
return false;
if (!altSendData(buf, false, smtp, msg, false, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
return false;
if (!sendBlobAttachment(smtp, msg, att))
return false;
if (!sendBDAT(smtp, msg, 2, false))
return false;
MB_String str;
appendNewline(str);
if (!altSendData(str, false, smtp, msg, false, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
return false;
}
else
{
if (att->file.storage_type == esp_mail_file_storage_type_sd && !smtp->_sdStorageChecked && !smtp->_sdStorageReady)
{
smtp->_sdStorageChecked = true;
smtp->_sdStorageReady = mbfs->sdReady();
}
else if (att->file.storage_type == esp_mail_file_storage_type_flash && !smtp->_flashStorageChecked && !smtp->_flashStorageReady)
{
smtp->_flashStorageChecked = true;
smtp->_flashStorageReady = mbfs->flashReady();
}
if (!smtp->_flashStorageReady && !smtp->_sdStorageReady)
{
sendStorageNotReadyError(smtp, att->file.storage_type);
continue;
}
if (openFileRead(smtp, msg, att, buf, boundary, false))
{
if (!sendFile(smtp, msg, att))
return false;
if (!sendBDAT(smtp, msg, 2, false))
return false;
MB_String str;
appendNewline(str);
if (!altSendData(str, false, smtp, msg, false, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
return false;
}
}
}
}
return true;
}
void ESP_Mail_Client::altSendStorageErrorCB(SMTPSession *smtp, int err)
{
#if defined(MBFS_FLASH_FS) || defined(MBFS_SD_FS)
if (smtp)
{
smtp->_responseStatus.errorCode = err;
smtp->_responseStatus.text.clear();
#if !defined(SILENT_MODE)
if (smtp->_statusCallback)
esp_mail_debug_print_tag(esp_mail_error_mem_str_3 /* "file does not exist or can't access" */, esp_mail_debug_tag_type_client, true);
if (smtp->_debug)
esp_mail_debug_print_tag(smtp->errorReason().c_str(), esp_mail_debug_tag_type_error, true);
#endif
}
else if (imap && !calDataLen)
{
#if defined(ENABLE_IMAP)
imap->_responseStatus.errorCode = err;
imap->_responseStatus.text.clear();
#if !defined(SILENT_MODE)
if (imap->_statusCallback)
esp_mail_debug_print_tag(esp_mail_error_mem_str_3 /* "file does not exist or can't access" */, esp_mail_debug_tag_type_client, true);
if (imap->_debug)
{
esp_mail_debug_print_tag(smtp->errorReason().c_str(), esp_mail_debug_tag_type_error, true);
}
#endif
#endif
}
#endif
}
bool ESP_Mail_Client::openFileRead(SMTPSession *smtp, SMTP_Message *msg, SMTP_Attachment *att, MB_String &buf, const MB_String &boundary, bool inlined)
{
int sz = -1;
MB_String filepath;
if (att->file.path.length() > 0)
{
if (att->file.path[0] != '/')
filepath = esp_mail_str_10; /* "/" */
filepath += att->file.path;
}
sz = mbfs->open(filepath, mbfs_type att->file.storage_type, mb_fs_open_mode_read);
if (sz < 0)
{
if (strlen(att->descr.filename.c_str()) > 0)
{
filepath.clear();
if (att->descr.filename[0] != '/')
filepath = esp_mail_str_10; /* "/" */
filepath += att->descr.filename;
}
sz = mbfs->open(filepath, mbfs_type att->file.storage_type, mb_fs_open_mode_read);
}
if (sz < 0)
{
altSendStorageErrorCB(smtp, sz);
}
else
{
buf.clear();
getAttachHeader(buf, boundary, att, sz, inlined);
if (!sendBDAT(smtp, msg, buf.length(), false))
return false;
if (!altSendData(buf, false, smtp, msg, false, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
return false;
return true;
}
return false;
}
bool ESP_Mail_Client::openFileRead2(SMTPSession *smtp, SMTP_Message *msg, const char *path, esp_mail_file_storage_type storageType)
{
MB_String filepath;
if (strlen(path) > 0)
{
if (path[0] != '/')
filepath = esp_mail_str_10; /* "/" */
filepath += path;
}
int sz = mbfs->open(filepath, mbfs_type storageType, mb_fs_open_mode_read);
if (sz < 0)
{
altSendStorageErrorCB(smtp, sz);
return false;
}
return true;
}
void ESP_Mail_Client::sendStorageNotReadyError(SMTPSession *smtp, esp_mail_file_storage_type storageType)
{
#if defined(MBFS_FLASH_FS) || defined(MBFS_SD_FS)
#if !defined(SILENT_MODE)
if (altIsCB(smtp))
{
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);
}
if (altIsDebug(smtp))
{
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
}
bool ESP_Mail_Client::sendInline(SMTPSession *smtp, SMTP_Message *msg, const MB_String &boundary, byte type)
{
size_t num = numAtt(smtp, esp_mail_att_type_inline, msg) > 0;
#if !defined(SILENT_MODE)
if (num > 0)
altSendCallback(smtp, esp_mail_cb_str_8 /* "Sending inline data..." */, esp_mail_dbg_str_14 /* "send inline data" */, esp_mail_debug_tag_type_client, true, false);
#endif
MB_String buf;
MB_String related = getMIMEBoundary(15);
int cnt = 0;
SMTP_Attachment *att = nullptr;
MB_String s;
appendBoundaryString(s, boundary.c_str(), false, true);
appendMultipartContentType(s, esp_mail_multipart_type_related, related.c_str());
if (!sendBDAT(smtp, msg, s.length(), false))
return false;
if (!altSendData(s, false, smtp, msg, false, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
return false;
if (!sendPartText(smtp, msg, type, related.c_str()))
return false;
#if !defined(SILENT_MODE)
bool cb = altIsCB(smtp);
if (cb && numAtt(smtp, esp_mail_att_type_inline, msg) > 0)
esp_mail_debug_print();
#endif
if (num > 0)
{
for (uint8_t i = 0; i < msg->_att.size(); i++)
{
att = &msg->_att[i];
if (att->_int.att_type == esp_mail_att_type_inline)
{
#if !defined(SILENT_MODE)
altSendCallback(smtp, att->descr.filename.c_str(), att->descr.filename.c_str(), esp_mail_debug_tag_type_client, true, false);
#endif
cnt++;
if (att->file.storage_type == esp_mail_file_storage_type_none)
{
if (!att->blob.data)
continue;
if (att->blob.size == 0)
continue;
#if !defined(SILENT_MODE)
altSendCallback(smtp, att->descr.filename.c_str(), att->descr.filename.c_str(), esp_mail_debug_tag_type_client, true, false);
#endif
buf.clear();
getAttachHeader(buf, related, att, att->blob.size, true);
if (!sendBDAT(smtp, msg, buf.length(), false))
return false;
if (!altSendData(buf, false, smtp, msg, false, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
return false;
if (!sendBlobAttachment(smtp, msg, att))
return false;
if (!sendBDAT(smtp, msg, 2, false))
return false;
MB_String str;
appendNewline(str);
if (!altSendData(str, false, smtp, msg, false, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
return false;
}
else
{
if (att->file.storage_type == esp_mail_file_storage_type_sd && !smtp->_sdStorageChecked && !smtp->_sdStorageReady)
{
smtp->_sdStorageChecked = true;
smtp->_sdStorageReady = mbfs->sdReady();
}
else if (att->file.storage_type == esp_mail_file_storage_type_flash && !smtp->_flashStorageChecked && !smtp->_flashStorageReady)
{
smtp->_flashStorageChecked = true;
smtp->_flashStorageReady = mbfs->flashReady();
}
if (!smtp->_flashStorageReady && !smtp->_sdStorageReady)
{
sendStorageNotReadyError(smtp, att->file.storage_type);
continue;
}
if (openFileRead(smtp, msg, att, buf, related, true))
{
if (!sendFile(smtp, msg, att))
return false;
if (!sendBDAT(smtp, msg, 2, false))
return false;
MB_String str;
appendNewline(str);
if (!altSendData(str, false, smtp, msg, false, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
return false;
}
}
}
}
}
appendNewline(s);
appendBoundaryString(s, related.c_str(), true, true);
if (!sendBDAT(smtp, msg, s.length(), false))
return false;
if (!altSendData(s, false, smtp, msg, false, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
return false;
return true;
}
size_t ESP_Mail_Client::smtpSend(SMTPSession *smtp, PGM_P data, bool newline)
{
if (!smtp || !sessionReady<SMTPSession *>(smtp))
return 0;
int sent = 0;
MB_String s = data;
int toSend = newline ? s.length() + 2 : s.length();
#if !defined(SILENT_MODE)
if (!smtp->_customCmdResCallback && smtp->_debugLevel > esp_mail_debug_level_maintainer)
esp_mail_debug_print(s.c_str(), newline);
#endif
sent = newline ? smtp->client.println(s.c_str()) : smtp->client.print(s.c_str());
if (sent != toSend)
{
errorStatusCB<SMTPSession *, IMAPSession *>(smtp, this->imap, sent, false);
sent = 0;
}
return sent;
}
size_t ESP_Mail_Client::smtpSend(SMTPSession *smtp, int data, bool newline)
{
MB_String s = data;
return smtpSend(smtp, s.c_str(), newline);
}
size_t ESP_Mail_Client::smtpSend(SMTPSession *smtp, uint8_t *data, size_t size)
{
if (!smtp || !sessionReady<SMTPSession *>(smtp))
return 0;
size_t sent = smtp->client.write(data, size);
if (sent != size)
{
errorStatusCB<SMTPSession *, IMAPSession *>(smtp, this->imap, sent, false);
sent = 0;
}
return sent;
}
bool ESP_Mail_Client::handleSMTPError(SMTPSession *smtp, int err, bool ret)
{
if (err < 0)
errorStatusCB<SMTPSession *, IMAPSession *>(smtp, this->imap, err, false);
if (smtp)
{
closeTCPSession<SMTPSession *>(smtp);
}
else if (imap && !calDataLen)
{
#if defined(ENABLE_IMAP)
if (imap->connected())
closeTCPSession<IMAPSession *>(imap);
#endif
}
return ret;
}
bool ESP_Mail_Client::sendPartText(SMTPSession *smtp, SMTP_Message *msg, uint8_t type, const char *boundary)
{
MB_String header;
if (strlen(boundary) > 0)
appendBoundaryString(header, boundary, false, true);
if (type == esp_mail_msg_type_plain || type == esp_mail_msg_type_enriched)
{
if (msg->text.content_type.length() > 0)
{
appendHeaderField(header, message_headers[esp_mail_message_header_field_content_type].text, msg->text.content_type.c_str(), false, false);
bool firstProp = true;
if (msg->text.charSet.length() > 0)
appendHeaderProp(header, message_headers[esp_mail_message_header_field_charset].text, msg->text.charSet.c_str(), firstProp, true, true, false);
if (msg->text.flowed)
{
appendHeaderProp(header, message_headers[esp_mail_message_header_field_format].text, "flowed", firstProp, true, true, false);
appendHeaderProp(header, message_headers[esp_mail_message_header_field_delsp].text, "no", firstProp, true, true, false);
}
if (msg->text.embed.enable)
{
appendHeaderProp(header, message_headers[esp_mail_message_header_field_name].text, esp_mail_str_13 /* "msg.txt" */, firstProp, false, true, false);
char *uid = getRandomUID();
msg->text._int.cid = uid;
// release memory
freeMem(&uid);
}
appendNewline(header);
}
if (msg->text.transfer_encoding.length() > 0)
appendHeaderField(header, message_headers[esp_mail_message_header_field_content_transfer_encoding].text, msg->text.transfer_encoding.c_str(), false, true);
}
else if (type == esp_mail_msg_type_html)
{
if (msg->text.content_type.length() > 0)
{
appendHeaderField(header, message_headers[esp_mail_message_header_field_content_type].text, msg->html.content_type.c_str(), false, false);
bool firstProp = true;
if (msg->html.charSet.length() > 0)
appendHeaderProp(header, message_headers[esp_mail_message_header_field_charset].text, msg->html.charSet.c_str(), firstProp, true, true, false);
if (msg->html.embed.enable)
{
appendHeaderProp(header, message_headers[esp_mail_message_header_field_name].text, esp_mail_str_14 /* "msg.html" */, firstProp, true, true, false);
char *uid = getRandomUID();
msg->html._int.cid = uid;
// release memory
freeMem(&uid);
}
appendNewline(header);
}
if (msg->html.transfer_encoding.length() > 0)
appendHeaderField(header, message_headers[esp_mail_message_header_field_content_transfer_encoding].text, msg->html.transfer_encoding.c_str(), false, true);
}
if ((type == esp_mail_msg_type_html && msg->html.embed.enable) || ((type == esp_mail_msg_type_plain || type == esp_mail_msg_type_enriched) && msg->text.embed.enable))
{
if ((type == esp_mail_msg_type_plain || type == esp_mail_msg_type_enriched) && msg->text.embed.enable)
appendEmbedMessage(header, msg->text, false);
else if (type == esp_mail_msg_type_html && msg->html.embed.enable)
appendEmbedMessage(header, msg->html, true);
}
appendNewline(header);
bool rawBlob = (msg->text.blob.size > 0 && (type == esp_mail_msg_type_plain || type == esp_mail_msg_type_enriched)) || (msg->html.blob.size > 0 && type == esp_mail_msg_type_html);
bool rawFile = (msg->text.file.name.length() > 0 && (type == esp_mail_msg_type_plain || type == esp_mail_msg_type_enriched)) || (msg->html.file.name.length() > 0 && type == esp_mail_msg_type_html);
bool rawContent = ((type == esp_mail_msg_type_plain || type == esp_mail_msg_type_enriched) && msg->text.content.length() > 0) || (type == esp_mail_msg_type_html && msg->html.content.length() > 0);
bool nonCopyContent = ((type == esp_mail_msg_type_plain || type == esp_mail_msg_type_enriched) && strlen(msg->text.nonCopyContent) > 0) || (type == esp_mail_msg_type_html && strlen(msg->html.nonCopyContent) > 0);
if (rawBlob || rawFile || nonCopyContent)
{
if (!sendBDAT(smtp, msg, header.length(), false))
return false;
if (!altSendData(header, false, smtp, msg, false, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
return false;
header.clear();
if (rawBlob || nonCopyContent)
{
if (!sendBlobBody(smtp, msg, type))
return false;
}
else if (rawFile)
{
if (!sendFileBody(smtp, msg, type))
return false;
}
}
else if (rawContent)
encodingText(smtp, msg, type, header);
appendNewline(header);
if (strlen(boundary) > 0)
appendNewline(header);
if (!sendBDAT(smtp, msg, header.length(), false))
return false;
if (!altSendData(header, false, smtp, msg, false, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
return false;
return true;
}
bool ESP_Mail_Client::sendBlobBody(SMTPSession *smtp, SMTP_Message *msg, uint8_t type)
{
bool cb = altIsCB(smtp);
uint32_t addr = altProgressPtr(smtp);
if (msg->text.blob.size == 0 && msg->html.blob.size == 0 && strlen(msg->text.nonCopyContent) == 0 && strlen(msg->html.nonCopyContent) == 0)
return true;
bool ret = true;
int bufLen = 512;
size_t pos = 0;
const uint8_t *raw = NULL;
int len = 0;
bool base64 = false;
if ((type == esp_mail_msg_type_plain || type == esp_mail_msg_type_enriched))
{
if (strlen(msg->text.nonCopyContent) > 0)
{
raw = (const uint8_t *)msg->text.nonCopyContent;
len = strlen(msg->text.nonCopyContent);
}
else
{
raw = msg->text.blob.data;
len = msg->text.blob.size;
}
base64 = msg->text.transfer_encoding.length() > 0 && strcmp(msg->text.transfer_encoding.c_str(), Content_Transfer_Encoding::enc_base64) == 0;
}
else if (type == esp_mail_msg_type_html)
{
if (strlen(msg->html.nonCopyContent) > 0)
{
raw = (const uint8_t *)msg->html.nonCopyContent;
len = strlen(msg->html.nonCopyContent);
}
else
{
raw = msg->html.blob.data;
len = msg->html.blob.size;
}
base64 = msg->html.transfer_encoding.length() > 0 && strcmp(msg->html.transfer_encoding.c_str(), Content_Transfer_Encoding::enc_base64) == 0;
}
if (base64)
{
MB_String s1 = esp_mail_str_15; /* "flash content message" */
esp_mail_smtp_send_base64_data_info_t data_info;
data_info.flashMem = true;
data_info.filename = s1.c_str();
data_info.rawPtr = raw;
data_info.size = len;
return sendBase64(smtp, msg, data_info, true, cb);
}
int available = len;
int sz = len;
uint8_t *buf = allocMem<uint8_t *>(bufLen + 1);
while (available)
{
if (available > bufLen)
available = bufLen;
memcpy_P(buf, raw + pos, available);
if (!sendBDAT(smtp, msg, available, false))
{
ret = false;
break;
}
if (!altSendData(buf, available, smtp, msg, false, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
{
ret = false;
break;
}
pos += available;
len -= available;
available = len;
if (smtp->_debug)
{
MB_String s1 = esp_mail_str_15; /* "flash content message" */
uploadReport(s1.c_str(), addr, 100 * pos / sz);
}
}
// release memory
freeMem(&buf);
return ret;
}
bool ESP_Mail_Client::sendFileBody(SMTPSession *smtp, SMTP_Message *msg, uint8_t type)
{
bool cb = altIsCB(smtp);
uint32_t addr = altProgressPtr(smtp);
if (msg->text.file.name.length() == 0 && msg->html.file.name.length() == 0)
return true;
bool ret = true;
int chunkSize = ESP_MAIL_CLIENT_STREAM_CHUNK_SIZE;
int writeLen = 0;
if (type == esp_mail_msg_type_plain || type == esp_mail_msg_type_enriched)
{
if (!openFileRead2(smtp, msg, msg->text.file.name.c_str(), msg->text.file.type))
return false;
if (msg->text.transfer_encoding.length() > 0)
{
if (strcmp(msg->text.transfer_encoding.c_str(), Content_Transfer_Encoding::enc_base64) == 0)
{
MB_String s1 = esp_mail_str_16; /* "file content message" */
esp_mail_smtp_send_base64_data_info_t data_info;
data_info.filename = s1.c_str();
data_info.storageType = msg->text.file.type;
return sendBase64(smtp, msg, data_info, true, cb);
}
}
int fileSize = mbfs->size(mbfs_type msg->text.file.type);
if (fileSize > 0)
{
if (fileSize < chunkSize)
chunkSize = fileSize;
uint8_t *buf = allocMem<uint8_t *>(chunkSize);
while (writeLen < fileSize && mbfs->available(mbfs_type msg->text.file.type))
{
if (writeLen > fileSize - chunkSize)
chunkSize = fileSize - writeLen;
int readLen = mbfs->read(mbfs_type msg->text.file.type, buf, chunkSize);
if (readLen != chunkSize)
{
errorStatusCB<SMTPSession *, IMAPSession *>(smtp, this->imap, MB_FS_ERROR_FILE_IO_ERROR, false);
break;
}
if (!sendBDAT(smtp, msg, chunkSize, false))
{
ret = false;
break;
}
if (!altSendData(buf, chunkSize, smtp, msg, false, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
{
ret = false;
break;
}
if (smtp->_debug)
uploadReport(pgm2Str(esp_mail_str_16 /* "file content message" */), addr, 100 * writeLen / fileSize);
writeLen += chunkSize;
}
// release memory
freeMem(&buf);
if (smtp->_debug)
uploadReport(pgm2Str(esp_mail_str_16 /* "file content message" */), addr, 100);
return ret && writeLen == fileSize;
}
}
else if (type == esp_mail_message_type::esp_mail_msg_type_html)
{
if (!openFileRead2(smtp, msg, msg->html.file.name.c_str(), msg->html.file.type))
return false;
if (msg->html.transfer_encoding.length() > 0)
{
if (strcmp(msg->html.transfer_encoding.c_str(), Content_Transfer_Encoding::enc_base64) == 0)
{
MB_String s1 = esp_mail_str_16; /* "file content message" */
esp_mail_smtp_send_base64_data_info_t data_info;
data_info.filename = s1.c_str();
data_info.storageType = msg->html.file.type;
return sendBase64(smtp, msg, data_info, true, cb);
}
}
int fileSize = mbfs->size(mbfs_type msg->html.file.type);
if (fileSize > 0)
{
if (fileSize < chunkSize)
chunkSize = fileSize;
uint8_t *buf = allocMem<uint8_t *>(chunkSize);
while (writeLen < fileSize && mbfs->available(mbfs_type msg->html.file.type))
{
if (writeLen > fileSize - chunkSize)
chunkSize = fileSize - writeLen;
int readLen = mbfs->read(mbfs_type msg->html.file.type, buf, chunkSize);
if (readLen != chunkSize)
{
errorStatusCB<SMTPSession *, IMAPSession *>(smtp, this->imap, MB_FS_ERROR_FILE_IO_ERROR, false);
break;
}
if (!sendBDAT(smtp, msg, chunkSize, false))
{
ret = false;
break;
}
if (!altSendData(buf, chunkSize, smtp, msg, false, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
{
ret = false;
break;
}
if (cb)
uploadReport(pgm2Str(esp_mail_str_16 /* "file content message" */), addr, 100 * writeLen / fileSize);
writeLen += chunkSize;
}
// release memory
freeMem(&buf);
if (cb)
uploadReport(pgm2Str(esp_mail_str_16 /* "file content message" */), addr, 100);
return ret && writeLen == fileSize;
}
}
return false;
}
void ESP_Mail_Client::encodingText(SMTPSession *smtp, SMTP_Message *msg, uint8_t type, MB_String &content)
{
if (type == esp_mail_msg_type_plain || type == esp_mail_msg_type_enriched)
{
MB_String s = msg->text.content;
if (msg->text.flowed)
formatFlowedText(s);
if (msg->text.transfer_encoding.length() > 0)
{
if (strcmp(msg->text.transfer_encoding.c_str(), Content_Transfer_Encoding::enc_base64) == 0)
content += encodeBase64Str((const unsigned char *)s.c_str(), s.length());
else if (strcmp(msg->text.transfer_encoding.c_str(), Content_Transfer_Encoding::enc_qp) == 0)
{
char *out = allocMem<char *>(s.length() * 3 + 1);
encodeQP(s.c_str(), out);
content += out;
// release memory
freeMem(&out);
}
else
content += s;
}
else
content += s;
}
else if (type == esp_mail_message_type::esp_mail_msg_type_html)
{
MB_String s = msg->html.content;
MB_String fnd, rep;
SMTP_Attachment *att = nullptr;
for (uint8_t i = 0; i < msg->_att.size(); i++)
{
att = &msg->_att[i];
if (att->_int.att_type == esp_mail_att_type_inline)
{
MB_String filename(att->descr.filename);
size_t found = filename.find_last_of("/\\");
if (found != MB_String::npos)
filename = filename.substr(found + 1);
appendString(fnd, filename.c_str(), false, false, esp_mail_string_mark_type_double_quote);
MB_String cid = esp_mail_str_17; /* "cid:" */
cid += att->descr.content_id.length() > 0 ? att->descr.content_id : att->_int.cid;
appendString(rep, cid.c_str(), false, false, esp_mail_string_mark_type_double_quote);
s.replaceAll(fnd, rep);
}
}
if (msg->html.transfer_encoding.length() > 0)
{
if (strcmp(msg->html.transfer_encoding.c_str(), Content_Transfer_Encoding::enc_base64) == 0)
content += encodeBase64Str((const unsigned char *)s.c_str(), s.length());
else if (strcmp(msg->html.transfer_encoding.c_str(), Content_Transfer_Encoding::enc_qp) == 0)
{
char *out = allocMem<char *>(msg->html.content.length() * 3 + 1);
encodeQP(msg->html.content.c_str(), out);
content += out;
// release memory
freeMem(&out);
}
else
content += s;
}
else
content += s;
s.clear();
}
if (msg->timestamp.tag.length() && msg->timestamp.format.length())
content.replaceAll(msg->timestamp.tag, Time.getDateTimeString(Time.getCurrentTimestamp(), msg->timestamp.format.c_str()));
}
void ESP_Mail_Client::encodeQP(const char *buf, char *out)
{
int n = 0, p = 0, pos = 0;
for (n = 0; *buf; buf++)
{
if (n >= 73 && *buf != 10 && *buf != 13)
{
p = sprintf(out + pos, "=\r\n");
pos += p;
n = 0;
}
if (*buf == 10 || *buf == 13)
{
out[pos++] = *buf;
n = 0;
}
else if (*buf < 32 || *buf == 61 || *buf > 126)
{
p = sprintf(out + pos, "=%02X", (unsigned char)*buf);
n += p;
pos += p;
}
else if (*buf != 32 || (*(buf + 1) != 10 && *(buf + 1) != 13))
{
out[pos++] = *buf;
n++;
}
else
{
p = sprintf(out + pos, "=20");
n += p;
pos += p;
}
}
}
/** Add the soft line break to the long text line (rfc 3676)
* and add Format=flowed parameter in the plain text content-type header.
* We use the existing white space as a part of this soft line break
* and set delSp="no" parameter to the header.
*
* Some servers are not rfc 3676 compliant.
* This causes the text lines are wrapped instead of joined.
*
* Some mail clients trim the space before the line break
* which makes the soft line break cannot be seen.
*/
void ESP_Mail_Client::formatFlowedText(MB_String &content)
{
int count = 0;
MB_String qms;
int j = 0;
_vectorImpl<MB_String> tokens;
char *stk = strP(esp_mail_str_18); /* "\r\n" */
char *qm = strP(esp_mail_str_20); /* ">" */
splitToken(content.c_str(), tokens, stk);
content.clear();
for (size_t i = 0; i < tokens.size(); i++)
{
if (tokens[i].length() > 0)
{
j = 0;
qms.clear();
while (tokens[i][j] == qm[0])
{
qms += qm;
j++;
}
softBreak(tokens[i], qms.c_str());
if (count > 0)
content += stk;
content += tokens[i];
}
else if (count > 0)
content += stk;
count++;
}
// release memory
freeMem(&stk);
freeMem(&qm);
tokens.clear();
}
void ESP_Mail_Client::softBreak(MB_String &content, const char *quoteMarks)
{
size_t len = 0;
char *stk = strP(esp_mail_str_2); /* " " */
_vectorImpl<MB_String> tokens;
splitToken(content.c_str(), tokens, stk);
content.clear();
for (size_t i = 0; i < tokens.size(); i++)
{
if (tokens[i].length() > 0)
{
if (len + tokens[i].length() + 3 > FLOWED_TEXT_LEN)
{
/* insert soft crlf */
content += stk;
appendNewline(content);
/* insert quote marks */
if (strlen(quoteMarks) > 0)
content += quoteMarks;
content += tokens[i];
len = tokens[i].length();
}
else
{
if (len > 0)
{
content += stk;
len += strlen(stk);
}
content += tokens[i];
len += tokens[i].length();
}
}
}
// release memory
freeMem(&stk);
tokens.clear();
}
bool ESP_Mail_Client::altSendData(MB_String &s, bool newLine, SMTPSession *smtp, SMTP_Message *msg, bool addSendResult, bool getResponse, esp_mail_smtp_command cmd, esp_mail_smtp_status_code statusCode, int errCode)
{
if (!imap && smtp)
{
if (smtpSend(smtp, s.c_str(), newLine) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED)
{
if (addSendResult)
return addSendingResult(smtp, msg, false, true);
else
return false;
}
if (getResponse)
{
if (!handleSMTPResponse(smtp, cmd, statusCode, errCode))
{
if (addSendResult)
return addSendingResult(smtp, msg, false, true);
else
return false;
}
}
}
else if (imap)
{
#if defined(ENABLE_IMAP)
if (newLine)
appendNewline(s);
MB_StringPtr data = toStringPtr(s);
if (calDataLen)
dataLen += s.length();
else
return imap->mSendData(data, false, esp_mail_imap_cmd_append);
#endif
}
return true;
}
bool ESP_Mail_Client::altSendData(uint8_t *data, size_t size, SMTPSession *smtp, SMTP_Message *msg, bool addSendResult, bool getResponse, esp_mail_smtp_command cmd, esp_mail_smtp_status_code statusCode, int errCode)
{
if (!imap && smtp)
{
if (smtpSend(smtp, data, size) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED)
{
if (addSendResult)
return addSendingResult(smtp, msg, false, true);
else
return false;
}
if (getResponse)
{
if (!handleSMTPResponse(smtp, cmd, statusCode, errCode))
{
if (addSendResult)
return addSendingResult(smtp, msg, false, true);
else
return false;
}
}
}
else if (imap)
{
#if defined(ENABLE_IMAP)
if (calDataLen)
dataLen += size;
else
return imap->mSendData(data, size, false, esp_mail_imap_cmd_append);
#endif
}
return true;
}
bool ESP_Mail_Client::sendMSG(SMTPSession *smtp, SMTP_Message *msg, const MB_String &boundary)
{
MB_String s;
if (numAtt(smtp, esp_mail_att_type_inline, msg) > 0)
{
appendMultipartContentType(s, esp_mail_multipart_type_alternative, boundary.c_str());
if (!sendBDAT(smtp, msg, s.length(), false))
return false;
if (!altSendData(s, false, smtp, msg, false, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
return false;
if (msg->type == esp_mail_msg_type_plain || msg->type == esp_mail_msg_type_enriched || msg->type == esp_mail_msg_type_html)
{
if (!sendInline(smtp, msg, boundary, msg->type))
return false;
}
else if (msg->type == (esp_mail_msg_type_html | esp_mail_msg_type_enriched | esp_mail_msg_type_plain))
{
if (!sendPartText(smtp, msg, esp_mail_msg_type_plain, boundary.c_str()))
return false;
if (!sendInline(smtp, msg, boundary, esp_mail_msg_type_html))
return false;
}
appendBoundaryString(s, boundary.c_str(), true, true);
if (!sendBDAT(smtp, msg, s.length(), false))
return false;
if (!altSendData(s, false, smtp, msg, false, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
return false;
}
else
{
if (msg->type == esp_mail_msg_type_plain || msg->type == esp_mail_msg_type_enriched || msg->type == esp_mail_msg_type_html)
{
if (!sendPartText(smtp, msg, msg->type, ""))
return false;
}
else if (msg->type == (esp_mail_msg_type_html | esp_mail_msg_type_enriched | esp_mail_msg_type_plain))
{
appendMultipartContentType(s, esp_mail_multipart_type_alternative, boundary.c_str());
if (!sendBDAT(smtp, msg, s.length(), false))
return false;
if (!altSendData(s, false, smtp, msg, false, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
return false;
if (!sendPartText(smtp, msg, esp_mail_msg_type_plain, boundary.c_str()))
return false;
if (!sendPartText(smtp, msg, esp_mail_msg_type_html, boundary.c_str()))
return false;
s.clear();
appendBoundaryString(s, boundary.c_str(), true, false);
if (!sendBDAT(smtp, msg, s.length(), false))
return false;
if (!altSendData(s, false, smtp, msg, true, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
return false;
}
}
return true;
}
void ESP_Mail_Client::getAttachHeader(MB_String &header, const MB_String &boundary, SMTP_Attachment *attach, size_t size, bool isInline)
{
appendBoundaryString(header, boundary.c_str(), false, true);
appendHeaderName(header, message_headers[esp_mail_message_header_field_content_type].text);
if (attach->descr.mime.length() == 0)
{
MB_String mime;
mimeFromFile(attach->descr.filename.c_str(), mime);
if (mime.length() > 0)
appendString(header, mime.c_str(), false, false);
else
appendString(header, mimeinfo[esp_mail_file_extension_binary].mimeType, false, false);
}
else
appendString(header, attach->descr.mime.c_str(), false, false);
MB_String filename = attach->descr.filename;
size_t found = filename.find_last_of("/\\");
if (found != MB_String::npos)
filename = filename.substr(found + 1);
bool firstProp = true;
appendHeaderProp(header, message_headers[esp_mail_message_header_field_name].text, filename.c_str(), firstProp, false, true, true);
if (isInline || (!isInline && !attach->_int.parallel))
{
appendHeaderName(header, message_headers[esp_mail_message_header_field_content_disposition].text);
appendString(header, isInline ? esp_mail_content_disposition_type_t::inline_ : esp_mail_content_disposition_type_t::attachment, false, false);
firstProp = true;
appendHeaderProp(header, message_headers[esp_mail_message_header_field_filename].text, filename.c_str(), firstProp, true, true, false);
appendHeaderProp(header, message_headers[esp_mail_message_header_field_size].text, MB_String((int)size).c_str(), firstProp, true, false, true);
}
if (isInline)
{
appendHeaderField(header, message_headers[esp_mail_message_header_field_content_location].text, filename.c_str(), false, true);
appendHeaderName(header, message_headers[esp_mail_message_header_field_content_id].text);
if (attach->descr.content_id.length() > 0)
appendString(header, attach->descr.content_id.c_str(), false, true, esp_mail_string_mark_type_angle_bracket);
else
appendString(header, attach->_int.cid.c_str(), false, true, esp_mail_string_mark_type_angle_bracket);
}
if (attach->descr.transfer_encoding.length() > 0)
appendHeaderField(header, message_headers[esp_mail_message_header_field_content_transfer_encoding].text, attach->descr.transfer_encoding.c_str(), false, true);
if (attach->descr.description.length() > 0)
appendHeaderField(header, message_headers[esp_mail_message_header_field_content_description].text, attach->descr.description.c_str(), false, true);
appendNewline(header);
}
void ESP_Mail_Client::getRFC822PartHeader(SMTPSession *smtp, MB_String &header, const MB_String &boundary)
{
appendBoundaryString(header, boundary.c_str(), false, true);
appendHeaderName(header, message_headers[esp_mail_message_header_field_content_type].text);
appendString(header, esp_mail_str_22 /* "message/rfc822" */, false, true);
appendHeaderName(header, message_headers[esp_mail_message_header_field_content_disposition].text);
appendString(header, esp_mail_content_disposition_type_t::attachment, false, true);
appendNewline(header);
}
uint32_t ESP_Mail_Client::altProgressPtr(SMTPSession *smtp)
{
uint32_t addr = 0;
if (smtp)
{
smtp->_lastProgress = -1;
addr = toAddr(smtp->_lastProgress);
}
else if (imap && !calDataLen)
{
#if defined(ENABLE_IMAP)
imap->_lastProgress = -1;
addr = toAddr(imap->_lastProgress);
#endif
}
return addr;
}
void ESP_Mail_Client::parseAuthCapability(SMTPSession *smtp, char *buf)
{
if (!smtp)
return;
if (strposP(buf, smtp_cmd_post_tokens[esp_mail_smtp_command_auth].c_str(), 0) > -1)
{
for (int i = esp_mail_auth_capability_plain; i < esp_mail_auth_capability_maxType; i++)
{
if (strposP(buf, smtp_auth_cap_pre_tokens[i].c_str(), 0) > -1)
{
smtp->_auth_capability[i] = true;
// Don't exit the loop
// and continue checking for all auth types
}
}
}
else if (strposP(buf, smtp_auth_capabilities[esp_mail_auth_capability_starttls].text, 0) > -1)
{
smtp->_auth_capability[esp_mail_auth_capability_starttls] = true;
return;
}
for (int i = esp_mail_smtp_send_capability_binary_mime; i < esp_mail_smtp_send_capability_maxType; i++)
{
if (strposP(buf, smtp_send_cap_pre_tokens[i].c_str(), 0) > -1)
{
smtp->_feature_capability[i] = true;
return;
}
}
}
bool ESP_Mail_Client::handleSMTPResponse(SMTPSession *smtp, esp_mail_smtp_command cmd, esp_mail_smtp_status_code statusCode, int errCode)
{
if (!smtp)
return false;
smtp->_smtp_cmd = cmd;
if (!reconnect(smtp))
return false;
bool ret = false;
char *response = nullptr;
int readLen = 0;
long dataTime = millis();
int chunkBufSize = 0;
MB_String s, r, err;
int chunkIndex = 0;
int count = 0;
bool completedResponse = false;
smtp->_responseStatus.errorCode = 0;
smtp->_responseStatus.statusCode = 0;
smtp->_responseStatus.text.clear();
uint8_t minResLen = 5;
struct esp_mail_smtp_response_status_t status;
bool canForward = smtp->_canForward;
smtp->_canForward = false;
status.id = smtp->_commandID;
chunkBufSize = smtp->client.available();
while (smtp->connected() && chunkBufSize <= 0)
{
if (!reconnect(smtp, dataTime))
return false;
if (!connected<SMTPSession *>(smtp))
{
if (cmd != esp_mail_smtp_cmd_logout)
errorStatusCB<SMTPSession *, IMAPSession *>(smtp, this->imap, MAIL_CLIENT_ERROR_CONNECTION_CLOSED, false);
return false;
}
chunkBufSize = smtp->client.available();
yield_impl();
}
dataTime = millis();
if (chunkBufSize > 1)
{
while (!completedResponse)
{
yield_impl();
if (!reconnect(smtp, dataTime))
return false;
if (!connected<SMTPSession *>(smtp))
{
if (cmd != esp_mail_smtp_cmd_logout)
errorStatusCB<SMTPSession *, IMAPSession *>(smtp, this->imap, MAIL_CLIENT_ERROR_CONNECTION_CLOSED, false);
return false;
}
chunkBufSize = smtp->client.available();
if (chunkBufSize <= 0)
break;
if (chunkBufSize > 0)
{
chunkBufSize = ESP_MAIL_CLIENT_RESPONSE_BUFFER_SIZE;
response = allocMem<char *>(chunkBufSize + 1);
read_line:
MB_String ovfBuf;
if (!readResponse<SMTPSession *>(smtp, response, chunkBufSize, readLen, false, count, ovfBuf))
{
closeTCPSession<SMTPSession *>(smtp);
errorStatusCB<SMTPSession *, IMAPSession *>(smtp, this->imap, MAIL_CLIENT_ERROR_READ_TIMEOUT, false);
return false;
}
// If buffer overflown, copy from overflow buffer
if (ovfBuf.length() > 0)
{
// release memory
freeMem(&response);
response = allocMem<char *>(ovfBuf.length() + 1);
strcpy(response, ovfBuf.c_str());
ovfBuf.clear();
}
if (readLen)
{
if (smtp->_smtp_cmd != esp_mail_smtp_command::esp_mail_smtp_cmd_initial_state)
{
// sometimes server sent multiple lines response
// sometimes rx buffer is not ready for a while
if (strlen(response) < minResLen)
{
r += response;
chunkBufSize = 0;
while (chunkBufSize == 0)
{
yield_impl();
if (!reconnect(smtp, dataTime))
return false;
chunkBufSize = smtp->client.available();
}
}
else
{
if (r.length() > 0)
{
r += response;
memset(response, 0, chunkBufSize);
strcpy(response, r.c_str());
}
#if !defined(SILENT_MODE)
if (!smtp->_customCmdResCallback && smtp->_debugLevel > esp_mail_debug_level_basic)
esp_mail_debug_print((const char *)response, true);
#endif
}
if (smtp->_smtp_cmd == esp_mail_smtp_command::esp_mail_smtp_cmd_greeting)
parseAuthCapability(smtp, response);
}
getResponseStatus(response, statusCode, 0, status);
// No response code from greeting?
// Assumed multi-line greeting responses.
if (status.statusCode == 0 && smtp->_smtp_cmd == esp_mail_smtp_command::esp_mail_smtp_cmd_initial_state)
{
#if !defined(SILENT_MODE)
if (smtp->_debug && !smtp->_customCmdResCallback)
esp_mail_debug_print_tag(response, esp_mail_debug_tag_type_server, true);
#endif
memset(response, 0, chunkBufSize + 1);
// read again until we get the response code
goto read_line;
}
// get the status code again for unexpected return code
if (smtp->_smtp_cmd == esp_mail_smtp_command::esp_mail_smtp_cmd_start_tls || status.statusCode == 0)
getResponseStatus(response, esp_mail_smtp_status_code_0, 0, status);
smtp->_responseStatus = status;
if ((status.statusCode > 0 && status.statusCode == statusCode) ||
(smtp->_smtp_cmd == esp_mail_smtp_cmd_start_tls && status.statusCode == esp_mail_smtp_status_code_220) ||
(canForward && statusCode == esp_mail_smtp_status_code_250 && status.statusCode == esp_mail_smtp_status_code_251)
)
{
ret = true;
}
if (strlen(response) >= minResLen)
{
#if !defined(SILENT_MODE)
if (smtp->_debug)
{
if (!smtp->_customCmdResCallback)
{
appendDebugTag(s, esp_mail_debug_tag_type_server, true);
if (smtp->_responseStatus.statusCode != esp_mail_smtp_status_code_334)
s += response;
else
{
// base64 encoded server challenge message
size_t olen = 0;
char *decoded = (char *)decodeBase64((const unsigned char *)status.text.c_str(), status.text.length(), &olen);
if (decoded && olen > 0)
{
s.append(decoded, olen);
err = decoded;
}
// release memory
freeMem(&decoded);
}
esp_mail_debug_print(s.c_str(), true);
}
}
#endif
r.clear();
}
if (smtp->_customCmdResCallback && (smtp->_smtp_cmd == esp_mail_smtp_command::esp_mail_smtp_cmd_initial_state || smtp->_smtp_cmd == esp_mail_smtp_command::esp_mail_smtp_cmd_custom))
{
struct esp_mail_smtp_response_status_t res = status;
res.text = response;
smtp->_customCmdResCallback(res);
}
completedResponse = smtp->_responseStatus.statusCode > 0 && status.text.length() > minResLen;
if (smtp->_smtp_cmd == esp_mail_smtp_command::esp_mail_smtp_cmd_auth_xoauth2 && smtp->_responseStatus.statusCode == esp_mail_smtp_status_code_334)
{
if (isOAuthError(response, readLen, chunkIndex, 4))
{
smtp->_responseStatus.text = err;
smtp->_responseStatus.errorCode = SMTP_STATUS_XOAUTH2_AUTH_FAILED;
ret = false;
}
}
chunkIndex++;
if (smtp->_chunkedEnable && smtp->_smtp_cmd == esp_mail_smtp_command::esp_mail_smtp_cmd_chunk_termination)
completedResponse = smtp->_chunkCount == chunkIndex;
}
// release memory
freeMem(&response);
}
}
if (!ret && !smtp->_customCmdResCallback)
handleSMTPError(smtp, errCode, false);
}
return ret;
}
void ESP_Mail_Client::getResponseStatus(const char *buf, esp_mail_smtp_status_code statusCode, int beginPos, struct esp_mail_smtp_response_status_t &status)
{
if (statusCode > esp_mail_smtp_status_code_0)
{
int codeLength = 3;
int textLength = strlen(buf) - codeLength - 1;
if ((int)strlen(buf) > codeLength && (buf[codeLength] == ' ' || buf[codeLength] == '-') && textLength > 0)
{
char *tmp = nullptr;
tmp = allocMem<char *>(codeLength + 1);
memcpy(tmp, &buf[0], codeLength);
int code = atoi(tmp);
// release memory
freeMem(&tmp);
if (buf[codeLength] == ' ')
status.statusCode = code;
int i = codeLength + 1;
// We will collect the status text starting from status code 334 and 4xx
// The status text of 334 will be used for debugging display of the base64 server challenge
if (code == esp_mail_smtp_status_code_334 || code >= esp_mail_smtp_status_code_421)
{
// find the next sp
while (i < (int)strlen(buf) && buf[i] != ' ')
i++;
// if sp found, set index to the next pos, otherwise set index to num length + 1
i = (i < (int)strlen(buf) - 1) ? i + 1 : codeLength + 1;
tmp = allocMem<char *>(textLength + 1);
memcpy(tmp, &buf[i], strlen(buf) - i - 1);
status.text += tmp;
// release memory
freeMem(&tmp);
}
}
}
}
void ESP_Mail_Client::uploadReport(const char *filename, uint32_t pgAddr, int progress)
{
if (pgAddr == 0)
return;
int *lastProgress = addrTo<int *>(pgAddr);
printProgress(progress, *lastProgress);
}
MB_String ESP_Mail_Client::getMIMEBoundary(size_t len)
{
MB_String tmp = boundary_table;
char *buf = allocMem<char *>(len);
if (len)
{
--len;
buf[0] = tmp[0];
buf[1] = tmp[1];
for (size_t n = 2; n < len; n++)
{
int key = rand() % (int)(tmp.length() - 1);
buf[n] = tmp[key];
}
buf[len] = '\0';
}
MB_String s = buf;
// release memory
freeMem(&buf);
return s;
}
int ESP_Mail_Client::chunkAvailable(SMTPSession *smtp, esp_mail_smtp_send_base64_data_info_t &data_info)
{
if (!data_info.rawPtr)
{
int fileSize = mbfs->size(mbfs_type data_info.storageType);
if (!fileSize)
{
errorStatusCB<SMTPSession *, IMAPSession *>(smtp, this->imap, MB_FS_ERROR_FILE_IO_ERROR, false);
return -1;
}
return mbfs->available(mbfs_type data_info.storageType);
}
return data_info.size - data_info.dataIndex;
}
int ESP_Mail_Client::getChunk(SMTPSession *smtp, esp_mail_smtp_send_base64_data_info_t &data_info, unsigned char *rawChunk, bool base64)
{
int available = chunkAvailable(smtp, data_info);
if (available <= 0)
return available;
size_t size = base64 ? 3 : 4;
if (data_info.dataIndex + size > data_info.size)
size = data_info.size - data_info.dataIndex;
if (!data_info.rawPtr)
{
int readLen = mbfs->read(mbfs_type data_info.storageType, rawChunk, size);
if (readLen >= 0)
data_info.dataIndex += readLen;
return readLen;
}
if (data_info.flashMem)
memcpy_P(rawChunk, data_info.rawPtr + data_info.dataIndex, size);
else
memcpy(rawChunk, data_info.rawPtr + data_info.dataIndex, size);
data_info.dataIndex += size;
return size;
}
void ESP_Mail_Client::closeChunk(esp_mail_smtp_send_base64_data_info_t &data_info)
{
if (!data_info.rawPtr)
{
mbfs->close(mbfs_type data_info.storageType);
}
}
bool ESP_Mail_Client::sendBase64(SMTPSession *smtp, SMTP_Message *msg, esp_mail_smtp_send_base64_data_info_t &data_info, bool base64, bool report)
{
int size = chunkAvailable(smtp, data_info);
if (size <= 0)
return false;
data_info.size = size;
bool ret = false;
uint32_t addr = altProgressPtr(smtp);
size_t chunkSize = (BASE64_CHUNKED_LEN * UPLOAD_CHUNKS_NUM) + (2 * UPLOAD_CHUNKS_NUM);
int bufIndex = 0;
bool dataReady = false;
int encodedCount = 0;
int read = 0;
if (!base64)
{
if (data_info.size < chunkSize)
chunkSize = data_info.size;
}
uint8_t *buf = allocMem<uint8_t *>(chunkSize);
memset(buf, 0, chunkSize);
uint8_t *rawChunk = allocMem<uint8_t *>(base64 ? 3 : 4);
if (report)
uploadReport(data_info.filename, addr, data_info.dataIndex / data_info.size);
int min = base64 ? 3 : 1;
while (chunkAvailable(smtp, data_info))
{
if (chunkAvailable(smtp, data_info) >= min)
{
read = getChunk(smtp, data_info, rawChunk, base64);
if (!read)
goto ex;
getBuffer(base64, buf, rawChunk, encodedCount, bufIndex, dataReady, read, chunkSize);
if (dataReady)
{
if (!sendBDAT(smtp, msg, base64 ? bufIndex : bufIndex + 1, false))
goto ex;
if (!altSendData(buf, base64 ? bufIndex : bufIndex + 1, smtp, msg, false, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
goto ex;
memset(buf, 0, chunkSize);
bufIndex = 0;
}
if (report)
uploadReport(data_info.filename, addr, 100 * data_info.dataIndex / data_info.size);
}
else if (base64)
{
read = getChunk(smtp, data_info, rawChunk, base64);
if (!read)
goto ex;
}
}
closeChunk(data_info);
if (base64)
{
if (bufIndex > 0)
{
if (!sendBDAT(smtp, msg, bufIndex, false))
goto ex;
if (!altSendData(buf, bufIndex, smtp, msg, false, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
goto ex;
}
if (read)
{
memset(buf, 0, chunkSize);
bufIndex = 0;
buf[bufIndex++] = b64_index_table[rawChunk[0] >> 2];
if (read == 1)
{
buf[bufIndex++] = b64_index_table[(rawChunk[0] & 0x03) << 4];
buf[bufIndex++] = '=';
}
else
{
buf[bufIndex++] = b64_index_table[((rawChunk[0] & 0x03) << 4) | (rawChunk[1] >> 4)];
buf[bufIndex++] = b64_index_table[(rawChunk[1] & 0x0f) << 2];
}
buf[bufIndex++] = '=';
if (!sendBDAT(smtp, msg, bufIndex, false))
goto ex;
if (!altSendData(buf, bufIndex, smtp, msg, false, false, esp_mail_smtp_cmd_undefined, esp_mail_smtp_status_code_0, SMTP_STATUS_UNDEFINED))
goto ex;
}
}
ret = true;
if (report)
uploadReport(data_info.filename, addr, 100);
ex:
// release memory
freeMem(&buf);
freeMem(&rawChunk);
if (!ret)
closeChunk(data_info);
return ret;
}
void ESP_Mail_Client::getBuffer(bool base64, uint8_t *out, uint8_t *in, int &encodedCount, int &bufIndex, bool &dataReady, int &size, size_t chunkSize)
{
if (base64)
{
size = 0;
out[bufIndex++] = b64_index_table[in[0] >> 2];
out[bufIndex++] = b64_index_table[((in[0] & 0x03) << 4) | (in[1] >> 4)];
out[bufIndex++] = b64_index_table[((in[1] & 0x0f) << 2) | (in[2] >> 6)];
out[bufIndex++] = b64_index_table[in[2] & 0x3f];
encodedCount += 4;
if (encodedCount == BASE64_CHUNKED_LEN)
{
if (bufIndex + 1 < (int)chunkSize)
{
out[bufIndex++] = 0x0d;
out[bufIndex++] = 0x0a;
}
encodedCount = 0;
}
dataReady = bufIndex >= (int)chunkSize - 4;
}
else
{
memcpy(out + bufIndex, in, size);
bufIndex += size;
if (bufIndex + 1 == BASE64_CHUNKED_LEN)
{
if (bufIndex + 2 < (int)chunkSize)
{
out[bufIndex++] = 0x0d;
out[bufIndex++] = 0x0a;
}
}
dataReady = bufIndex + 1 >= (int)chunkSize - size;
}
}
MB_FS *ESP_Mail_Client::getMBFS()
{
return mbfs;
}
SMTPSession::SMTPSession(Client *client)
{
setClient(client);
}
SMTPSession::SMTPSession()
{
}
SMTPSession::~SMTPSession()
{
closeSession();
}
bool SMTPSession::connect(Session_Config *session_config, 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, _sessionSSL))
return false;
if (!_sessionLogin)
return true;
_loginStatus = MailClient.smtpAuth(this, _sessionSSL);
return _loginStatus;
}
bool SMTPSession::mLogin(MB_StringPtr email, MB_StringPtr password, bool isToken)
{
if (_loginStatus)
return true;
if (!MailClient.sessionExisted<SMTPSession *>(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.smtpAuth(this, _sessionSSL);
return _loginStatus;
}
bool SMTPSession::isAuthenticated()
{
return _authenticated;
}
bool SMTPSession::isLoggedIn()
{
return _loginStatus;
}
int SMTPSession::customConnect(Session_Config *session_config, smtpResponseCallback callback, int commandID)
{
this->_customCmdResCallback = callback;
if (commandID > -1)
this->_commandID = commandID;
else
this->_commandID++;
bool ssl = false;
if (!handleConnection(session_config, ssl))
return -1;
return this->_responseStatus.statusCode;
}
bool SMTPSession::handleConnection(Session_Config *session_config, bool &ssl)
{
_session_cfg = session_config;
if (!client.isInitialized())
return MailClient.handleSMTPError(this, TCP_CLIENT_ERROR_NOT_INITIALIZED);
// Resources are also released if network disconnected.
if (!MailClient.reconnect(this))
return false;
// Close previous connection first to free resources.
MailClient.closeTCPSession<SMTPSession *>(this);
_session_cfg = session_config;
MailClient.setCert(_session_cfg, _session_cfg->certificate.cert_data);
ssl = false;
if (!connect(ssl))
{
MailClient.closeTCPSession<SMTPSession *>(this);
return false;
}
return true;
}
bool SMTPSession::connect(bool &ssl)
{
if (!MailClient.reconnect(this))
return false;
ssl = false;
_secure = true;
bool secureMode = true;
MB_String s;
client.rxBufDivider = 16; // minimum rx buffer for smtp status response
client.txBufDivider = 8; // medium tx buffer for faster attachment/inline data transfer
MailClient.preparePortFunction(_session_cfg, true, _secure, secureMode, ssl);
#if !defined(SILENT_MODE)
MailClient.printLibInfo<SMTPSession *>(this);
#endif
MailClient.prepareTime<SMTPSession *>(_session_cfg, this);
MailClient.setSecure(client, _session_cfg);
if (!MailClient.beginConnection<SMTPSession *>(_session_cfg, this, secureMode))
return false;
// server connected
#if !defined(SILENT_MODE)
if (!_customCmdResCallback)
{
MailClient.printDebug<SMTPSession *>(this,
esp_mail_dbg_str_4 /* "SMTP server connected" */,
esp_mail_cb_str_12 /* "SMTP server connected, wait for greeting..." */,
esp_mail_debug_tag_type_client,
true,
false);
}
#endif
client.setTimeout(tcpTimeout);
// expected success status code 220 for ready to service
// expected failure status code 421
if (!MailClient.handleSMTPResponse(this, esp_mail_smtp_cmd_initial_state, esp_mail_smtp_status_code_220, SMTP_STATUS_SMTP_GREETING_GET_RESPONSE_FAILED))
return false;
return true;
}
int SMTPSession::mSendCustomCommand(MB_StringPtr cmd, smtpResponseCallback callback, int commandID)
{
_customCmdResCallback = callback;
if (commandID > -1)
_commandID = commandID;
else
_commandID++;
MB_String _cmd = cmd;
if (MailClient.smtpSend(this, _cmd.c_str(), true) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED)
return -1;
if (!MailClient.handleSMTPResponse(this, esp_mail_smtp_cmd_custom, esp_mail_smtp_status_code_0, SMTP_STATUS_SEND_CUSTOM_COMMAND_FAILED))
return -1;
bool tlsCmd = false;
for (int i = esp_mail_auth_capability_plain; i < esp_mail_auth_capability_maxType; i++)
{
if (MailClient.strposP(_cmd.c_str(), smtp_auth_capabilities[i].text, 0) > -1)
{
if (i == esp_mail_auth_capability_starttls)
tlsCmd = true;
else
_waitForAuthenticate = true;
}
}
if (MailClient.strposP(_cmd.c_str(), smtp_commands[esp_mail_smtp_command_quit].text, 0, false) > -1)
{
_authenticated = false;
_waitForAuthenticate = false;
}
if (_waitForAuthenticate && _responseStatus.statusCode == esp_mail_smtp_status_code_235)
{
_authenticated = true;
_waitForAuthenticate = false;
_loginStatus = true;
}
if (tlsCmd)
{
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 this->_responseStatus.statusCode;
}
bool SMTPSession::mSendData(MB_StringPtr data)
{
MB_String _data = data;
if (MailClient.smtpSend(this, _data.c_str(), false) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED)
return false;
return true;
}
bool SMTPSession::mSendData(uint8_t *data, size_t size)
{
if (MailClient.smtpSend(this, data, size) == ESP_MAIL_CLIENT_TRANSFER_DATA_FAILED)
return false;
return true;
}
void SMTPSession::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 SMTPSession::errorReason()
{
String s = MailClient.errorReason(true, _responseStatus.errorCode, "");
return s;
}
int SMTPSession::statusCode()
{
return _responseStatus.statusCode;
}
String SMTPSession::statusMessage()
{
return _responseStatus.text.c_str();
}
int SMTPSession::errorCode()
{
return _responseStatus.errorCode;
}
bool SMTPSession::closeSession()
{
if (!connected())
return false;
_cbData._sentSuccess = _sentSuccessCount;
_cbData._sentFailed = _sentFailedCount;
#if !defined(SILENT_MODE)
MailClient.printDebug<SMTPSession *>(this,
esp_mail_cb_str_7 /* "Closing the session..." */,
esp_mail_dbg_str_11 /* "terminate the SMTP session" */,
esp_mail_debug_tag_type_client,
true,
false);
#endif
bool ret = true;
if (_loginStatus)
{
/* Sign out */
#if !defined(ESP8266)
// QUIT command asks SMTP server to close the TCP session.
// The connection may drop immediately.
// There is memory leaks bug in ESP8266 BearSSLWiFiClientSecure class when the remote server
// drops the connection.
ret = MailClient.smtpSend(this, smtp_commands[esp_mail_smtp_command_quit].text, true) > 0;
// This may return false due to connection drops before get any response.
// expected success status code 221
// expected error status code 500
MailClient.handleSMTPResponse(this, esp_mail_smtp_cmd_logout, esp_mail_smtp_status_code_221, SMTP_STATUS_SEND_BODY_FAILED);
if (_responseStatus.statusCode == esp_mail_smtp_status_code_500)
return false;
#endif
if (ret)
{
#if !defined(SILENT_MODE)
if (_sentSuccessCount > 0)
{
MailClient.printDebug<SMTPSession *>(this,
esp_mail_cb_str_13 /* "Message sent successfully" */,
esp_mail_dbg_str_12 /* "message sent successfully" */,
esp_mail_debug_tag_type_client,
true,
false);
}
if (_statusCallback)
MailClient.callBackSendNewLine<SMTPSession *>(this, true);
#endif
_authenticated = false;
_waitForAuthenticate = false;
}
}
#if !defined(SILENT_MODE)
if (_statusCallback)
sendingResult.clear();
#endif
return MailClient.handleSMTPError(this, 0, ret);
}
bool SMTPSession::connected()
{
return client.connected();
}
void SMTPSession::callback(smtpStatusCallback smtpCallback)
{
_statusCallback = smtpCallback;
}
SMTP_Status SMTPSession::status()
{
return _cbData;
}
void SMTPSession::setSystemTime(time_t ts, float gmtOffset)
{
MailClient.Time.setTimestamp(ts, gmtOffset);
}
void SMTPSession::keepAlive(int tcpKeepIdleSeconds, int tcpKeepIntervalSeconds, int tcpKeepCount)
{
this->client.keepAlive(tcpKeepIdleSeconds, tcpKeepIntervalSeconds, tcpKeepCount);
}
bool SMTPSession::isKeepAlive()
{
return this->client.isKeepAlive();
}
void SMTPSession::setTCPTimeout(unsigned long timeoutSec)
{
tcpTimeout = timeoutSec;
}
void SMTPSession::setClient(Client *client)
{
this->client.setClient(client);
}
void SMTPSession::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 SMTPSession::networkConnectionRequestCallback(NetworkConnectionRequestCallback networkConnectionCB)
{
this->client.networkConnectionRequestCallback(networkConnectionCB);
}
void SMTPSession::networkStatusRequestCallback(NetworkStatusRequestCallback networkStatusCB)
{
this->client.networkStatusRequestCallback(networkStatusCB);
}
void SMTPSession::setNetworkStatus(bool status)
{
this->client.setNetworkStatus(status);
MailClient.networkStatus = status;
}
void SMTPSession::setSSLBufferSize(int rx, int tx)
{
this->client.setIOBufferSize(rx, tx);
}
SMTP_Status::SMTP_Status()
{
}
SMTP_Status::~SMTP_Status()
{
empty();
}
const char *SMTP_Status::info()
{
return _info.c_str();
}
bool SMTP_Status::success()
{
return _success;
}
size_t SMTP_Status::completedCount()
{
return _sentSuccess;
}
size_t SMTP_Status::failedCount()
{
return _sentFailed;
}
void SMTP_Status::empty()
{
_info.clear();
}
#endif
#endif /* ESP_MAIL_SMTP_H */