1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
|
/*
Copyright (©) 2003-2025 Teus Benschop.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <filter/mail.h>
#include <filter/string.h>
#ifdef HAVE_CLOUD
// https://www.codesink.org/mimetic_mime_library.html
// Suppress warnings in the included header.
#pragma GCC diagnostic push
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
#pragma clang diagnostic ignored "-Wdeprecated-register"
#pragma clang diagnostic ignored "-Wunused-private-field"
#pragma clang diagnostic ignored "-Wimplicit-int-conversion"
#pragma clang diagnostic ignored "-Wconversion"
#pragma clang diagnostic ignored "-Wdocumentation"
#pragma clang diagnostic ignored "-Wconditional-uninitialized"
#pragma clang diagnostic ignored "-Wshadow"
#pragma GCC diagnostic ignored "-Weffc++"
#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
#pragma GCC diagnostic ignored "-Wshadow"
#pragma GCC diagnostic ignored "-Wconversion"
#pragma GCC diagnostic ignored "-Wold-style-cast"
#pragma GCC diagnostic ignored "-Wsuggest-override"
#pragma GCC diagnostic ignored "-Wswitch-default"
#include <mimetic098/mimetic.h>
#pragma clang diagnostic pop
#pragma GCC diagnostic pop
std::string filter_mail_remove_headers_internal (std::string contents)
{
bool empty_line_encountered = false;
std::vector <std::string> cleaned;
std::vector <std::string> inputlines = filter::strings::explode (contents, '\n');
for (auto line : inputlines) {
if (line.find ("Content-Type") != std::string::npos) continue;
if (line.find ("Content-Transfer-Encoding") != std::string::npos) continue;
if (empty_line_encountered) cleaned.push_back (line);
if (filter::strings::trim (line).empty ()) empty_line_encountered = true;
}
contents = filter::strings::implode (cleaned, "\n");
contents = filter::strings::trim (contents);
return contents;
}
void filter_mail_dissect_internal (const mimetic::MimeEntity& me, std::string& plaintext)
{
// If the plain text of this email has been found already,
// there's no need to search any further.
if (!plaintext.empty ()) return;
// Get the header of this part.
const mimetic::Header& h = me.header();
// Look for content type and subtype.
// Fold their case as some messages use upper case.
const std::string type = filter::strings::unicode_string_casefold (h.contentType().type());
const std::string subtype = filter::strings::unicode_string_casefold (h.contentType().subtype());
if (type == "text") {
if (subtype== "plain") {
// Get the plain text of the message.
std::stringstream ss;
ss << me;
plaintext = ss.str ();
// Remove headers.
plaintext = filter_mail_remove_headers_internal (plaintext);
}
if (subtype== "html") {
// Get the html text of the message.
std::stringstream ss;
ss << me;
std::string html = ss.str ();
// Remove headers.
html = filter_mail_remove_headers_internal (html);
// Convert the html to plain text.
plaintext = filter::strings::html2text (html);
}
// Get transfer encoding.
// Fold the case as some email messages use uppercase.
std::string transfer_encoding = filter::strings::unicode_string_casefold (h.contentTransferEncoding().str ());
// Decode quoted-printable text.
if (transfer_encoding == mimetic::ContentTransferEncoding::quoted_printable) {
std::istringstream is (plaintext);
std::ostringstream os;
std::istreambuf_iterator<char> ibeg (is), iend;
std::ostreambuf_iterator<char> out (os);
mimetic::QP::Decoder qp;
decode (ibeg, iend, qp, out);
plaintext = os.str ();
}
// Decode base64 text.
if (transfer_encoding == mimetic::ContentTransferEncoding::base64) {
std::istringstream is (plaintext);
std::ostringstream os;
std::istreambuf_iterator<char> ibeg (is), iend;
std::ostreambuf_iterator<char> out (os);
mimetic::Base64::Decoder b64;
code (ibeg, iend, b64, out);
plaintext = os.str ();
}
}
// Iterate over the other parts it may contain and process them.
mimetic::MimeEntityList::const_iterator mime_body_iterator = me.body().parts().begin();
mimetic::MimeEntityList::const_iterator meit = me.body().parts().end();
for (; mime_body_iterator != meit; ++mime_body_iterator) {
filter_mail_dissect_internal (**mime_body_iterator, plaintext);
}
}
// Dissects an email $message.
// It extracts the $from address, the $subject, and the plain text body.
void filter_mail_dissect (std::string message, std::string & from, std::string & subject, std::string & plaintext)
{
// Load the email message into the mimetic library.
mimetic::MimeEntity me;
me.load (message.begin(), message.end(), 0);
// Get the sender's address.
std::stringstream fromstream;
fromstream << me.header().from();
from = fromstream.str ();
// Get the subject.
std::stringstream subjectstream;
subjectstream << me.header().subject();
subject = subjectstream.str ();
// Get the plain text body.
filter_mail_dissect_internal (me, plaintext);
// In case it's not a MIME message, the plain/text part will not be there.
// Take the email's entire body instead.
if (plaintext.empty ()) plaintext = me.body ();
// Clean the text body up.
std::vector <std::string> cleaned;
std::vector <std::string> inputlines = filter::strings::explode (plaintext, '\n');
for (auto line : inputlines) {
// Remove whitespace and empty lines.
line = filter::strings::trim (line);
if (line.empty ()) continue;
// If the line starts with ">", it indicates quoted text. Skip it.
if (line.substr (0, 1) == ">") continue;
// Store this line.
cleaned.push_back (line);
}
plaintext = filter::strings::implode (cleaned, "\n");
}
#endif
|