rssguard/src/librssguard/3rd-party/mimesis/mimesis.cpp
Robert-André Mauchin 3946bfd9b6
Force use of signed char in mimesis.cpp
Fix #270

Signed-off-by: Robert-André Mauchin <zebob.m@gmail.com>
2020-08-29 17:28:30 +02:00

1271 lines
31 KiB
C++
Executable file

// For license of this file, see <project-root-folder>/LICENSE.md.
/* Mimesis -- a library for parsing and creating RFC2822 messages
Copyright © 2017 Guus Sliepen <guus@lightbts.info>
Mimesis is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser 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 Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "mimesis.hpp"
#include "quoted-printable.hpp"
#include <algorithm>
#include <cstring>
#include <fstream>
#include <iostream>
#include <random>
#include <sstream>
#include <stdexcept>
#include <string_view>
#include <QDateTime>
#include <QLocale>
#include <QTextCodec>
using namespace std;
namespace Mimesis {
static const string base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const signed char base64_inverse[256] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
-1, 0, 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, -1, -1, -1, -1, -1,
-1, 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, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
};
string base64_encode(string_view in) {
string out;
size_t outlen = ((in.size() + 2) / 3) * 4;
out.reserve(outlen);
size_t i;
const uint8_t* uin = (const uint8_t*)in.data();
for (i = 0; i < (in.size() / 3) * 3; i += 3) {
out.push_back(base64[ (uin[i + 0] >> 2)]);
out.push_back(base64[(uin[i + 0] << 4 & 63) | (uin[i + 1] >> 4)]);
out.push_back(base64[(uin[i + 1] << 2 & 63) | (uin[i + 2] >> 6)]);
out.push_back(base64[(uin[i + 2] << 0 & 63) ]);
}
while (i++ < in.size())
out.push_back('=');
return out;
}
string base64_decode(string_view in) {
string out;
size_t estimate = (in.size() / 4) * 3;
out.reserve(estimate);
int i = 0;
uint32_t triplet = 0;
for(uint8_t c: in) {
auto d = base64_inverse[c];
if (d == -1) {
if (c == '=')
break;
else
continue;
}
triplet <<= 6;
triplet |= d;
if((i & 3) == 3) {
out.push_back(static_cast<char>(triplet >> 16));
out.push_back(static_cast<char>(triplet >> 8));
out.push_back(static_cast<char>(triplet));
}
i++;
}
if((i & 3) == 3) {
out.push_back(static_cast<char>(triplet >> 10));
out.push_back(static_cast<char>(triplet >> 2));
}
else if((i & 3) == 2) {
out.push_back(static_cast<char>(triplet >> 4));
}
return out;
}
static std::random_device rnd;
static string unquote(const string& str) {
if (str.empty() || str[0] != '"')
return str;
string unquoted;
int quotes_wanted = 2;
for (auto&& c: str) {
if (c == '"') {
if (--quotes_wanted)
continue;
break;
}
if (c == '\\')
continue;
unquoted.push_back(c);
}
return unquoted;
}
static string quote(const string& str) {
bool do_quote = false;
for (auto&& c: str) {
if (isalnum(c) || strchr("!#$%&'*+-/=?^_`{|}~", c))
continue;
do_quote = true;
break;
}
if (!do_quote)
return str;
string quoted = "\"";
for (auto&& c: str) {
if (c == '\"' || c == '\\')
quoted.push_back('\\');
quoted.push_back(c);
}
quoted.push_back('"');
return quoted;
}
static bool streqi(const string& a, const string& b) {
if (a.size() != b.size())
return false;
for (size_t i = 0; i < a.size(); i++)
if (tolower(a[i]) != tolower(b[i]))
return false;
return true;
}
static bool streqi(const string& a, size_t offset_a, size_t len_a, const string& b) {
if (min(a.size() - offset_a, len_a) != b.size())
return false;
for (size_t i = 0; i < len_a; i++)
if (tolower(a[i + offset_a]) != tolower(b[i]))
return false;
return true;
}
static bool streqi(const string& a, size_t offset_a, size_t len_a, const string& b, size_t offset_b, size_t len_b) {
if (min(a.size() - offset_a, len_a) != min(b.size() - offset_b, len_b))
return false;
for (size_t i = 0; i < min(a.size() - offset_a, len_a); i++)
if (tolower(a[i + offset_a]) != tolower(b[i + offset_b]))
return false;
return true;
}
static string generate_boundary() {
unsigned int nonce[24 / sizeof(unsigned int)];
for (auto& val: nonce)
val = rnd();
return base64_encode(string_view(reinterpret_cast<char*>(nonce), sizeof nonce));
}
static bool is_boundary(const std::string& line, const std::string& boundary) {
if (boundary.empty())
return false;
if (line.compare(0, 2, "--"))
return false;
if (line.compare(2, boundary.size(), boundary))
return false;
return true;
}
static bool is_final_boundary(const std::string& line, const std::string& boundary) {
if (line.compare(2 + boundary.size(), 2, "--"))
return false;
return is_boundary(line, boundary);
}
static bool types_match(const std::string& a, const std::string& b) {
auto a_slash = a.find('/');
auto b_slash = b.find('/');
if (a_slash == string::npos || b_slash == string::npos)
return streqi(a, 0, a_slash, b, 0, b_slash);
else
return streqi(a, b);
}
static void set_value(string& str, const string& value) {
size_t semicolon = str.find(';');
if (semicolon == string::npos)
str = value;
else
str.replace(0, semicolon, value);
}
static string get_value(const string& str) {
return str.substr(0, str.find(';'));
}
static pair<size_t, size_t> get_parameter_value_range(const string& str, const string& parameter) {
size_t start = 0;
size_t end = string::npos;
// Find a semicolon, which marks the start of a parameter.
while((start = str.find(';', start)) != string::npos) {
start++;
while (isspace(str[start]))
start++;
if (!streqi(str, start, parameter.size(), parameter)) {
// It's not the wanted parameter.
start = str.find('=', start);
while (isspace(str[start]))
start++;
if (str[start] != '=')
continue;
while (isspace(str[start]))
start++;
// If it's a quoted parameter, skip over the quoted text.
if (str[start] == '"') {
start++;
while (start < str.size() && str[start] != '"') {
if (str[start] == '\\' && str.size() > start - 1)
start++;
start++;
}
}
continue;
}
// Skip until we get to the value.
start += parameter.size();
while (isspace(str[start]))
start++;
if (str[start] != '=')
continue;
start++;
while (isspace(str[start]))
start++;
end = start;
if (str[end] == '"') {
// It's a quoted parameter.
end++;
while (end < str.size() && str[end] != '"') {
if (str[end] == '\\' && str.size() > end - 1)
end++;
end++;
}
}
else {
while (end < str.size() && str[end] != ';' && !isspace(str[end]))
end++;
}
break;
}
return make_pair(start, end);
}
static void set_parameter(string& str, const string& parameter, const string& value) {
auto range = get_parameter_value_range(str, parameter);
auto start = range.first;
auto end = range.second;
if (start == string::npos)
str += "; " + parameter + "=" + quote(value);
else
str.replace(start, end - start, quote(value));
}
static string get_parameter(const string& str, const string& parameter) {
auto range = get_parameter_value_range(str, parameter);
auto start = range.first;
auto end = range.second;
if (start == string::npos)
return {};
return unquote(str.substr(start, end - start));
}
static const string ending[2] = {"\n", "\r\n"};
Part::Part() :
headers(),
preamble(),
body(),
epilogue(),
parts(),
boundary(),
multipart(false),
crlf(true),
message(false)
{}
// Loading and saving a whole MIME message
string Part::load(istream& in, const string& parent_boundary) {
string line;
int ncrlf = 0;
int nlf = 0;
while (getline(in, line)) {
if (is_boundary(line, parent_boundary))
return line;
if (line.size() && line.back() == '\r') {
ncrlf++;
line.erase(line.size() - 1);
}
else {
nlf++;
}
if (line.empty())
break;
if (isspace(line[0])) {
if (headers.empty())
throw runtime_error("invalid header line");
headers.back().second.append(line);
continue;
}
size_t colon = string::npos;
for (size_t i = 0; i < line.size(); ++i) {
if (line[i] == ':') {
colon = i;
break;
}
if (line[i] < 33 || static_cast<uint8_t>(line[i]) > 127) {
if (i == 4 && line[i] == ' ' && line.compare(0, 4, "From") == 0 && headers.empty()) {
colon = i;
break;
}
throw runtime_error("invalid header line " + line + std::to_string(i));
}
}
if (colon == 0 || colon == string::npos)
throw runtime_error("invalid header line");
if (line[colon] != ':')
continue;
auto start = colon + 1;
while (start < line.size() && isspace(line[start]))
start++;
// Empty header values are allowed for most fields.
auto field = line.substr(0, colon);
auto value = line.substr(start);
headers.emplace_back(field, value);
}
crlf = ncrlf > nlf;
const string content_type = get_header("Content-Type");
if (types_match(get_value(content_type), "multipart")) {
boundary = get_parameter(content_type, "boundary");
if (boundary.empty())
throw runtime_error("multipart but no boundary specified");
multipart = true;
}
else {
multipart = false;
}
if (!multipart) {
while (getline(in, line)) {
if (is_boundary(line, parent_boundary))
return line;
line.push_back('\n');
body.append(line);
}
}
else {
while (getline(in, line)) {
if (is_boundary(line, parent_boundary))
return line;
if (is_boundary(line, boundary))
break;
line.push_back('\n');
preamble.append(line);
}
while (true) {
parts.emplace_back();
string last_line = parts.back().load(in, boundary);
if (!is_boundary(last_line, boundary))
throw runtime_error("invalid boundary");
if (is_final_boundary(last_line, boundary))
break;
}
while (getline(in, line)) {
if (is_boundary(line, parent_boundary))
return line;
line.push_back('\n');
epilogue.append(line);
}
}
if (in.bad())
throw runtime_error("error reading message");
return {};
}
void Part::save(ostream& out) const {
bool has_headers = false;
for (auto& header: headers) {
if (!header.second.empty()) {
out << header.first << ": " << header.second << ending[crlf];
has_headers = true;
}
}
if (message && !has_headers)
throw runtime_error("no headers specified");
out << ending[crlf];
if (parts.empty()) {
out << body;
}
else {
out << preamble;
for (auto& part: parts) {
out << "--" << boundary << ending[crlf];
part.save(out);
}
out << "--" << boundary << "--" << ending[crlf];
out << epilogue;
}
}
void Part::load(const string& filename) {
ifstream in(filename);
if (!in.is_open())
throw runtime_error("could not open message file");
load(in);
}
void Part::save(const string& filename) const {
ofstream out(filename);
if (!out.is_open())
throw runtime_error("could not open message file");
save(out);
out.close();
if (out.fail())
throw runtime_error("could not write message file");
}
void Part::from_string(const string& data) {
istringstream in(data);
load(in);
}
string Part::to_string() const {
ostringstream out;
save(out);
return out.str();
}
void Part::set_crlf(bool value) {
crlf = value;
}
// Low-level access
string charset_decode(const string& charset, string_view in) {
QTextCodec* custom_codec = QTextCodec::codecForName(charset.c_str());
auto unic = custom_codec->toUnicode(string(in).c_str());
std::string utf8_text = unic.toUtf8().constData();
return utf8_text;
}
string Part::get_body() const {
string result;
auto encoding = get_header_value("Content-Transfer-Encoding");
if (streqi(encoding, "quoted-printable"))
result = quoted_printable_decode(body);
if (streqi(encoding, "base64"))
result = base64_decode(body);
else
result = body;
if (is_mime_type("text")) {
auto charset = get_header_parameter("Content-Type", "charset");
if (!charset.empty() && !streqi(charset, "utf-8") && !streqi(charset, "us-ascii") && !streqi(charset, "ascii")) {
result = charset_decode(charset, result);
}
}
return result;
}
string Part::get_preamble() const {
return preamble;
}
string Part::get_epilogue() const {
return epilogue;
}
string Part::get_boundary() const {
return boundary;
}
vector<Part>& Part::get_parts() {
return parts;
}
const vector<Part>& Part::get_parts() const {
return parts;
}
vector<pair<string, string>>& Part::get_headers() {
return headers;
}
const vector<pair<string, string>>& Part::get_headers() const {
return headers;
}
bool Part::is_multipart() const {
return multipart;
}
bool Part::is_multipart(const std::string& subtype) const {
return multipart && get_header_value("Content-Type") == "multipart/" + subtype;
}
bool Part::is_singlepart() const {
return !multipart;
}
bool Part::is_singlepart(const std::string& type) const {
return !multipart && types_match(get_header_value("Content-Type"), type);
}
bool Part::is_attachment() const {
return get_header_value("Content-Disposition") == "attachment";
}
bool Part::is_inline() const {
return get_header_value("Content-Disposition") == "inline";
}
void Part::set_body(const string& value) {
if (multipart)
throw runtime_error("Cannot set body of a multipart message");
body = value;
}
void Part::set_preamble(const string& value) {
if (!multipart)
throw runtime_error("Cannot set preamble of a non-multipart message");
preamble = value;
}
void Part::set_epilogue(const string& value) {
if (!multipart)
throw runtime_error("Cannot set epilogue of a non-multipart message");
epilogue = value;
}
void Part::set_boundary(const std::string& value) {
boundary = value;
if (has_mime_type())
set_header_parameter("Content-Type", "boundary", boundary);
}
void Part::set_parts(const vector<Part>& value) {
if (!multipart)
throw runtime_error("Cannot set parts of a non-multipart message");
parts = value;
}
void Part::set_headers(const vector<pair<string, string>>& value) {
headers = value;
}
void Part::clear() {
headers.clear();
preamble.clear();
body.clear();
epilogue.clear();
parts.clear();
boundary.clear();
multipart = false;
}
void Part::clear_body() {
body.clear();
}
// Header manipulation
static bool iequals(const string& a, const string& b) {
if (a.size() != b.size())
return false;
for (size_t i = 0; i < a.size(); ++i)
if (tolower(a[i]) != tolower(b[i]))
return false;
return true;
}
string Part::get_header(const string& field) const {
for (const auto& header: headers)
if (iequals(header.first, field))
return header.second;
return {};
}
void Part::set_header(const string& field, const string& value) {
for (auto& header: headers) {
if (iequals(header.first, field)) {
header.second = value;
return;
}
}
append_header(field, value);
}
string& Part::operator[](const string& field) {
for (auto& header: headers)
if (iequals(header.first, field))
return header.second;
append_header(field, {});
return headers.back().second;
}
const string& Part::operator[](const string& field) const {
for (auto& header: headers)
if (iequals(header.first, field))
return header.second;
static string empty_string;
return empty_string;
}
void Part::append_header(const string& field, const string& value) {
headers.push_back(make_pair(field, value));
}
void Part::prepend_header(const string& field, const string& value) {
headers.insert(begin(headers), make_pair(field, value));
}
void Part::erase_header(const string& field) {
headers.erase(remove_if(begin(headers), end(headers), [&](pair<string, string>& header) {
return header.first == field;
}), end(headers));
}
void Part::clear_headers() {
headers.clear();
}
string Part::get_header_value(const string& field) const {
return get_value(get_header(field));
}
string Part::get_header_parameter(const string& field, const string& parameter) const {
return get_parameter(get_header(field), parameter);
}
void Part::set_header_value(const string& field, const string& value) {
for (auto& header: headers) {
if (iequals(header.first, field)) {
set_value(header.second, value);
return;
}
}
append_header(field, value);
}
void Part::set_header_parameter(const string& field, const string& parameter, const string& value) {
for (auto& header: headers) {
if (iequals(header.first, field)) {
set_parameter(header.second, parameter, value);
return;
}
}
append_header(field, "; " + parameter + "=" + value);
}
static string get_date_string(const chrono::system_clock::time_point& date = chrono::system_clock::now()) {
QLocale loc("C");
QDateTime dat;
dat.fromTime_t(date.time_since_epoch().count());
return loc.toString(dat, "ddd, MM MMM yyyy HH:mm:ss t").toStdString();
/*time_t t = chrono::system_clock::to_time_t(date);
struct tm tm{};
localtime_r(&t, &tm);
char str[128];
char *oldlocale = setlocale(LC_TIME, "C");
size_t result = strftime(str, sizeof str, "%a, %d %b %Y %T %z", &tm);
setlocale(LC_TIME, oldlocale);
if (result == 0)
throw runtime_error("Could not convert date to string");
return str;
*/
}
void Part::add_received(const string& text, const chrono::system_clock::time_point& date) {
prepend_header("Received", text + "; " + get_date_string(date));
}
void Part::generate_msgid(const string& domain) {
auto now = chrono::system_clock::now();
uint64_t buf[3];
buf[0] = ((uint64_t)rnd() << 32) | rnd();
buf[1] = chrono::duration_cast<chrono::microseconds>(now.time_since_epoch()).count();
buf[2] = ((uint64_t)rnd() << 32) | rnd();
string msgid = "<" + base64_encode(string_view(reinterpret_cast<char*>(buf), sizeof buf)) + "@" + domain + ">";
set_header("Message-ID", msgid);
}
void Part::set_date(const chrono::system_clock::time_point& date) {
set_header("Date", get_date_string(date));
}
// Part manipulation
Part& Part::append_part(const Part& part) {
parts.push_back(part);
return parts.back();
}
Part& Part::prepend_part(const Part& part) {
parts.insert(begin(parts), part);
return parts.front();
}
void Part::clear_parts() {
parts.clear();
}
void Part::make_multipart(const string& subtype, const string& suggested_boundary) {
if (multipart) {
if (is_multipart(subtype))
return;
Part part;
part.preamble = move(preamble);
part.epilogue = move(epilogue);
part.parts = move(parts);
part.boundary = move(boundary);
part.multipart = true;
part.set_header("Content-Type", get_header("Content-Type"));
part.set_header("Content-Disposition", get_header("Content-Disposition"));
erase_header("Content-Disposition");
part.crlf = crlf;
parts.emplace_back(move(part));
}
else {
multipart = true;
if (message)
set_header("MIME-Version", "1.0");
if (!body.empty()) {
auto& part = append_part();
part.set_header("Content-Type", get_header("Content-Type"));
part.set_header("Content-Disposition", get_header("Content-Disposition"));
erase_header("Content-Disposition");
part.body = move(body);
}
}
if (!suggested_boundary.empty())
set_boundary(suggested_boundary);
if (boundary.empty())
boundary = generate_boundary();
set_header("Content-Type", "multipart/" + subtype + "; boundary=" + boundary);
}
bool Part::flatten() {
if (!multipart)
return true;
if (parts.empty()) {
multipart = false;
return true;
}
if (parts.size() > 1)
return false;
auto& part = parts.front();
set_header("Content-Type", part.get_header("Content-Type"));
set_header("Content-Disposition", part.get_header("Content-Disposition"));
if (part.multipart) {
parts = move(part.parts);
}
else {
multipart = false;
set_body(part.get_body());
parts.clear();
}
return true;
}
// Body and attachments
string Part::get_mime_type() const {
return get_header_value("Content-Type");
}
void Part::set_mime_type(const std::string& type) {
return set_header_value("Content-Type", type);
}
bool Part::is_mime_type(const std::string& type) const {
return types_match(get_mime_type(), type);
}
bool Part::has_mime_type() const {
return !get_mime_type().empty();
}
const Part* Part::get_first_matching_part(function<bool(const Part&)> predicate) const {
if (!multipart) {
if (headers.empty() && body.empty())
return nullptr;
if (is_attachment())
return nullptr;
}
if (predicate(*this))
return this;
for (auto& part: parts) {
auto result = part.get_first_matching_part(predicate);
if (result)
return result;
}
return nullptr;
}
Part* Part::get_first_matching_part(function<bool(const Part&)> predicate) {
auto result = ((const Part*)this)->get_first_matching_part(predicate);
return const_cast<Part*>(result);
}
const Part* Part::get_first_matching_part(const string& type) const {
return get_first_matching_part([type](const Part& part) {
auto my_type = part.get_mime_type();
return types_match(my_type.empty() ? "text/plain" : my_type, type);
});
}
Part* Part::get_first_matching_part(const string& type) {
auto result = ((const Part*)this)->get_first_matching_part(type);
return const_cast<Part*>(result);
}
string Part::get_first_matching_body(const string& type) const {
const auto& part = get_first_matching_part(type);
if (part)
return part->get_body();
else
return {};
}
Part& Part::set_alternative(const string& subtype, const string& text) {
string type = "text/" + subtype;
Part* part = nullptr;
// Try to put it in the body first.
if (!multipart) {
if (body.empty() || is_mime_type(type)) {
part = this;
}
else if (is_mime_type("text") && !is_attachment()) {
make_multipart("alternative");
part = &append_part();
}
else {
make_multipart("mixed");
part = &prepend_part();
}
}
else {
// If there is already a text/plain part, use that one.
part = get_first_matching_part(type);
if (part) {
part->set_mime_type(type);
part->set_body(text);
return *part;
}
// If there is already a multipart/alternative with text, use that one.
part = get_first_matching_part([](const Part& part) {
return part.is_multipart("alternative")
&& !part.parts.empty()
&& part.get_first_matching_part("text");
});
if (part)
part = &part->append_part();
// If there is already inline text, make it multipart/alternative.
if (!part && (part = get_first_matching_part("text"))) {
part->make_multipart("alternative");
part = &part->append_part();
}
// Otherwise, assume we're multipart/mixed.
if (!part)
part = &prepend_part();
}
part->set_header("Content-Type", type);
part->set_body(text);
return *part;
}
void Part::set_plain(const string& text) {
set_alternative("plain", text);
}
void Part::set_html(const string& html) {
set_alternative("html", html);
}
string Part::get_plain() const {
return get_first_matching_body("text/plain");
}
string Part::get_html() const {
return get_first_matching_body("text/html");
}
string Part::get_text() const {
return get_first_matching_body("text");
}
Part& Part::attach(const Part& attachment) {
if (!multipart && body.empty()) {
if (attachment.message) {
set_header("Content-Type", "message/rfc822");
body = attachment.to_string();
}
else {
set_header("Content-Type", attachment.get_header("Content-Type"));
body = attachment.body;
}
set_header("Content-Disposition", "attachment");
return *this;
}
make_multipart("mixed");
auto& part = append_part();
if (attachment.message) {
part.set_header("Content-Type", "message/rfc822");
part.body = attachment.to_string();
}
else {
part.set_header("Content-Type", attachment.get_header("Content-Type"));
part.body = attachment.body;
}
part.set_header("Content-Disposition", "attachment");
return part;
}
Part& Part::attach(const string& data, const string& type, const string& filename) {
if (!multipart && body.empty()) {
set_header("Content-Type", type.empty() ? "text/plain" : type);
set_header("Content-Disposition", "attachment");
if (!filename.empty())
set_header_parameter("Content-Disposition", "filename", filename);
body = data;
return *this;
}
make_multipart("mixed");
auto& part = append_part();
part.set_header("Content-Type", type.empty() ? "text/plain" : type);
part.set_header("Content-Disposition", "attachment");
if (!filename.empty())
part.set_header_parameter("Content-Disposition", "filename", filename);
part.set_body(data);
return part;
}
Part& Part::attach(istream& in, const string& type, const string& filename) {
auto& part = attach("", type, filename);
char buffer[4096];
while (in.read(buffer, sizeof(buffer)))
part.body.append(buffer, sizeof(buffer));
part.body.append(buffer, in.gcount());
return part;
}
vector<const Part*> Part::get_attachments() const {
vector<const Part*> attachments;
if (!multipart && get_header_value("Content-Disposition") == "attachment") {
attachments.push_back(this);
return attachments;
}
for (auto& part: parts) {
auto sub = part.get_attachments();
attachments.insert(end(attachments), begin(sub), end(sub));
}
return attachments;
}
void Part::simplify() {
if (!multipart)
return;
for (auto& part: parts)
part.simplify();
parts.erase(remove_if(begin(parts), end(parts), [&](Part& part) {
return part.headers.empty() && part.body.empty();
}), end(parts));
if (parts.empty()) {
if (message) {
erase_header("Content-Type");
erase_header("Content-Disposition");
multipart = false;
}
else {
clear();
}
}
else if (parts.size() == 1) {
flatten();
}
}
void Part::clear_attachments() {
if (!multipart) {
if (get_header_value("Content-Disposition") == "attachment") {
if (message) {
erase_header("Content-Type");
erase_header("Content-Disposition");
body.clear();
}
else {
clear();
}
}
}
else {
for (auto& part: parts)
part.clear_attachments();
simplify();
}
}
void Part::clear_alternative(const string& type) {
bool cleared = false;
Part* part;
while((part = get_first_matching_part(type))) {
part->clear();
cleared = true;
}
if (cleared)
simplify();
}
void Part::clear_text() {
clear_alternative("text");
}
void Part::clear_plain() {
clear_alternative("text/plain");
}
void Part::clear_html() {
clear_alternative("text/html");
}
bool Part::has_text() const {
return get_first_matching_part("text");
}
bool Part::has_plain() const {
return get_first_matching_part("text/plain");
}
bool Part::has_html() const {
return get_first_matching_part("text/html");
}
bool Part::has_attachments() const {
if (is_attachment())
return true;
for (auto& part: parts)
if (part.has_attachments())
return true;
return false;
}
// RFC2822 messages
Message::Message() {
message = true;
}
// Comparison
bool operator==(const Part& lhs, const Part& rhs) {
return lhs.crlf == rhs.crlf && lhs.multipart == rhs.multipart &&
lhs.preamble == rhs.preamble && lhs.body == rhs.body &&
lhs.epilogue == rhs.epilogue && lhs.boundary == rhs.boundary &&
lhs.headers == rhs.headers && lhs.parts == rhs.parts;
}
bool operator!=(const Part& lhs, const Part& rhs) {
return !(lhs == rhs);
}
}