yalantinglibs/include/ylt/standalone/cinatra/smtp_client.hpp

213 lines
5.3 KiB
C++

#pragma once
#include <asio.hpp>
#include <string>
#include "utils.hpp"
namespace cinatra::smtp {
struct email_server {
std::string server;
std::string port;
std::string user;
std::string password;
};
struct email_data {
std::string from_email;
std::vector<std::string> to_email;
std::string subject;
std::string text;
std::string filepath;
};
template <typename T>
class client {
public:
static constexpr bool IS_SSL = std::is_same_v<T, cinatra::SSL>;
client(asio::io_service &io_service)
: io_context_(io_service), socket_(io_service), resolver_(io_service) {}
~client() { close(); }
void set_email_server(const email_server &server) { server_ = server; }
void set_email_data(const email_data &data) { data_ = data; }
void start() {
std::string host = server_.server;
size_t pos = host.find("://");
if (pos != std::string::npos) {
host.erase(0, pos + 3);
}
asio::ip::tcp::resolver::query qry(
host, server_.port, asio::ip::resolver_query_base::numeric_service);
std::error_code ec;
auto endpoint_iterator = resolver_.resolve(qry, ec);
asio::connect(socket_, endpoint_iterator, ec);
if (ec) {
return;
}
if constexpr (IS_SSL) {
upgrade_to_ssl();
}
build_request();
asio::write(socket(), request_, ec);
if (ec) {
return;
}
while (true) {
asio::read(socket(), response_, asio::transfer_at_least(1), ec);
if (ec) {
return;
}
std::stringstream stream;
stream << &response_;
std::string content = stream.str();
if (content.find("250 Mail OK") != std::string::npos) {
return;
}
}
}
private:
auto &socket() {
#ifdef CINATRA_ENABLE_SSL
if constexpr (IS_SSL) {
assert(ssl_socket_);
return *ssl_socket_;
}
else
#endif
{
return socket_;
}
}
void upgrade_to_ssl() {
#ifdef CINATRA_ENABLE_SSL
asio::ssl::context ctx(asio::ssl::context::sslv23);
ctx.set_default_verify_paths();
ctx.set_verify_mode(ctx.verify_fail_if_no_peer_cert);
ssl_socket_ = std::make_unique<asio::ssl::stream<asio::ip::tcp::socket &>>(
socket_, ctx);
ssl_socket_->set_verify_mode(asio::ssl::verify_none);
ssl_socket_->set_verify_callback([](auto preverified, auto &ctx) {
char subject_name[256];
X509 *cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);
return preverified;
});
std::error_code ec;
ssl_socket_->handshake(asio::ssl::stream_base::client, ec);
#endif
}
std::string load_file_contents(const std::string &filepath) {
std::ifstream fin(filepath.c_str(), std::ios::in | std::ios::binary);
if (!fin) {
throw std::invalid_argument("not exist");
}
std::ostringstream oss;
oss << fin.rdbuf();
return oss.str();
}
void build_smtp_content(std::ostream &out) {
out << "Content-Type: multipart/mixed; boundary=\"cinatra\"\r\n\r\n";
out << "--cinatra\r\nContent-Type: text/plain;\r\n\r\n";
out << data_.text << "\r\n\r\n";
}
void build_smtp_file(std::ostream &out) {
if (data_.filepath.empty()) {
return;
}
std::string filename =
std::filesystem::path(data_.filepath).filename().string();
out << "--cinatra\r\nContent-Type: application/octet-stream; name=\""
<< filename << "\"\r\n";
out << "Content-Transfer-Encoding: base64\r\n";
out << "Content-Disposition: attachment; filename=\"" << filename
<< "\"\r\n";
out << "\r\n";
std::string file_content = load_file_contents(data_.filepath);
size_t file_size = file_content.size();
std::string encoded = base64_encode(file_content);
int SEND_BUF_SIZE = 1024;
int no_of_rows = (int)file_size / SEND_BUF_SIZE + 1;
for (int i = 0; i != no_of_rows; ++i) {
std::string sub_buf = encoded.substr(i * SEND_BUF_SIZE, SEND_BUF_SIZE);
out << sub_buf << "\r\n";
}
}
void build_request() {
std::ostream out(&request_);
out << "EHLO " << server_.server << "\r\n";
out << "AUTH LOGIN\r\n";
out << base64_encode(server_.user) << "\r\n";
out << base64_encode(server_.password) << "\r\n";
out << "MAIL FROM:<" << data_.from_email << ">\r\n";
for (auto to : data_.to_email) out << "RCPT TO:<" << to << ">\r\n";
out << "DATA\r\n";
out << "FROM: " << data_.from_email << "\r\n";
for (auto to : data_.to_email) out << "TO: " << to << "\r\n";
out << "SUBJECT: " << data_.subject << "\r\n";
build_smtp_content(out);
build_smtp_file(out);
out << "--cinatra--\r\n";
out << ".\r\n";
}
void close() {
std::error_code ignore_ec;
if constexpr (IS_SSL) {
#ifdef CINATRA_ENABLE_SSL
ssl_socket_->shutdown(ignore_ec);
#endif
}
socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ignore_ec);
socket_.close(ignore_ec);
}
private:
asio::io_context &io_context_;
asio::ip::tcp::socket socket_;
#ifdef CINATRA_ENABLE_SSL
std::unique_ptr<asio::ssl::stream<asio::ip::tcp::socket &>> ssl_socket_;
#endif
asio::ip::tcp::resolver resolver_;
email_server server_;
email_data data_;
asio::streambuf request_;
asio::streambuf response_;
};
template <typename T>
static inline auto get_smtp_client(asio::io_service &io_service) {
return smtp::client<T>(io_service);
}
} // namespace cinatra::smtp