|
|
|
@ -25,14 +25,15 @@ namespace px = boost::phoenix;
|
|
|
|
|
namespace pEpMIME
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
typedef std::vector<Message> MultipartMessage;
|
|
|
|
|
|
|
|
|
|
std::vector<Message> parse_multipart(const BodyLines& body, const sv& boundary)
|
|
|
|
|
MultipartMessage parse_multipart(const BodyLines& body, const sv& boundary)
|
|
|
|
|
{
|
|
|
|
|
bool is_last = false;
|
|
|
|
|
qi::rule<const char*, qi::unused_type()> is_delimiter_parser = qi::lit("--") >> qi::lit(boundary.data())
|
|
|
|
|
>> -qi::lit("--")[ px::ref(is_last) = true] >> qi::omit[*qi::char_(" \t")];
|
|
|
|
|
|
|
|
|
|
std::vector<Message> vm;
|
|
|
|
|
MultipartMessage vm;
|
|
|
|
|
bool after_preamble = false;
|
|
|
|
|
BodyLines part;
|
|
|
|
|
LOG << "Parse_Multipart: " << body.size() << " body lines. bounardy=“" << boundary << "”. \n";
|
|
|
|
@ -70,8 +71,14 @@ std::vector<Message> parse_multipart(const BodyLines& body, const sv& boundary)
|
|
|
|
|
|
|
|
|
|
char* create_string(const BodyLines& body, const sv& charset, MimeHeaders::Decoder decoder)
|
|
|
|
|
{
|
|
|
|
|
if(body.empty())
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
|
|
size_t decoded_size = 0;
|
|
|
|
|
char* decoded = decoder(body, decoded_size);
|
|
|
|
|
|
|
|
|
|
LOG << "CREATE_STRING: " << body.size() << " body lines into " << decoded_size << " raw octets (charset=\"" << charset << "\")\n";
|
|
|
|
|
|
|
|
|
|
if(charset=="UTF-8" || charset=="UTF8")
|
|
|
|
|
{
|
|
|
|
|
return decoded; // fine. :-)
|
|
|
|
@ -119,85 +126,151 @@ struct has_mimetype
|
|
|
|
|
const char* mt;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// parses the header and fill the parts in msg
|
|
|
|
|
void parse_body(message* msg, const HeaderSection& headers, const BodyLines& body)
|
|
|
|
|
|
|
|
|
|
// handle multipart/alternative
|
|
|
|
|
void handle_multipart_alternative(message* msg, MultipartMessage& vm)
|
|
|
|
|
{
|
|
|
|
|
const std::string mime_version = header_value(headers, "mime-version").to_string();
|
|
|
|
|
MimeHeaders mh(headers);
|
|
|
|
|
// only add to msg->longmsg if not already set
|
|
|
|
|
auto first_text = msg->longmsg ? vm.cend() : std::find_if(vm.cbegin(), vm.cend(), has_mimetype("text/plain") );
|
|
|
|
|
if(first_text != vm.end())
|
|
|
|
|
{
|
|
|
|
|
LOG << "ALT-TEXT: MH" << first_text->mh << "\n";
|
|
|
|
|
const sv txt_charset = header_value( first_text->mh.tparams, "charset" );
|
|
|
|
|
msg->longmsg = create_string(first_text->body, txt_charset, first_text->mh.decoder );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( mime_version == "1.0" ) // TODO: According to RFC 2048 there can be comments in the header field value. -.-
|
|
|
|
|
// only add to msg->longmsg_formatted if not already set
|
|
|
|
|
auto first_html = msg->longmsg_formatted ? vm.cend() : std::find_if(vm.cbegin(), vm.cend(), has_mimetype("text/html") );
|
|
|
|
|
if(first_html != vm.end())
|
|
|
|
|
{
|
|
|
|
|
// TODO: for whatever reason "string_view cts" does not work with qi::parse(). WTF!
|
|
|
|
|
|
|
|
|
|
if(mh.type == "text")
|
|
|
|
|
{
|
|
|
|
|
const sv charset = header_value( mh.tparams, "charset" );
|
|
|
|
|
if(mh.subtype == "plain")
|
|
|
|
|
{
|
|
|
|
|
// put it in msg->longmsg
|
|
|
|
|
msg->longmsg = create_string(body, charset, mh.decoder);
|
|
|
|
|
}else if(mh.subtype=="html")
|
|
|
|
|
{
|
|
|
|
|
// put it in msg->longmsg_formatted
|
|
|
|
|
msg->longmsg_formatted = create_string(body, charset, mh.decoder);
|
|
|
|
|
}else{
|
|
|
|
|
// add it as attachment
|
|
|
|
|
add_attachment(msg, body, mh);
|
|
|
|
|
}
|
|
|
|
|
}else if(mh.type == "multipart")
|
|
|
|
|
LOG << "ALT-HTML: MH" << first_html->mh << "\n";
|
|
|
|
|
const sv html_charset = header_value( first_html->mh.tparams, "charset" );
|
|
|
|
|
msg->longmsg_formatted = create_string(first_html->body, html_charset, first_html->mh.decoder );
|
|
|
|
|
}else
|
|
|
|
|
{
|
|
|
|
|
auto mrel = std::find_if(vm.cbegin(), vm.cend(), has_mimetype("multipart/related") );
|
|
|
|
|
if(mrel != vm.end())
|
|
|
|
|
{
|
|
|
|
|
const sv boundary = header_value(mh.tparams, "boundary");
|
|
|
|
|
std::vector<Message> vm = parse_multipart( body, boundary );
|
|
|
|
|
LOG << "MULTIPART: " << vm.size() << " parts. Boundary = “" << boundary << "” :\n";
|
|
|
|
|
for(const auto& m : vm)
|
|
|
|
|
MultipartMessage vmr = parse_multipart( mrel->body, mrel->boundary() );
|
|
|
|
|
first_html = msg->longmsg_formatted ? vmr.cend() : std::find_if(vmr.cbegin(), vmr.cend(), has_mimetype("text/html") );
|
|
|
|
|
if(first_html != vmr.cend())
|
|
|
|
|
{
|
|
|
|
|
LOG << "####\n" << m << "\n";
|
|
|
|
|
LOG << "ALT-RELATED-HTML: MH" << first_html->mh << "\n";
|
|
|
|
|
const sv html_charset = header_value( first_html->mh.tparams, "charset" );
|
|
|
|
|
msg->longmsg_formatted = create_string(first_html->body, html_charset, first_html->mh.decoder );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(mh.subtype == "alternative")
|
|
|
|
|
// move the remaining parts to the topmost MIME tree
|
|
|
|
|
for(auto m = vmr.cbegin(); m != vmr.cend(); ++m)
|
|
|
|
|
{
|
|
|
|
|
auto first_text = std::find_if(vm.cbegin(), vm.cend(), has_mimetype("text/plain") );
|
|
|
|
|
if(first_text != vm.end())
|
|
|
|
|
if(m != first_html)
|
|
|
|
|
{
|
|
|
|
|
const sv charset = header_value( first_text->mh.tparams, "charset" );
|
|
|
|
|
msg->longmsg = create_string(first_text->body, charset, first_text->mh.decoder );
|
|
|
|
|
add_attachment(msg, m->body, m->mh);
|
|
|
|
|
}
|
|
|
|
|
auto first_html = std::find_if(vm.cbegin(), vm.cend(), has_mimetype("text/html") );
|
|
|
|
|
if(first_html != vm.end())
|
|
|
|
|
{
|
|
|
|
|
const sv charset = header_value( first_html->mh.tparams, "charset" );
|
|
|
|
|
msg->longmsg_formatted = create_string(first_html->body, charset, first_html->mh.decoder );
|
|
|
|
|
}
|
|
|
|
|
for(auto m = vm.cbegin(); m != vm.cend(); ++m)
|
|
|
|
|
{
|
|
|
|
|
if(m != first_text && m!=first_html)
|
|
|
|
|
{
|
|
|
|
|
add_attachment(msg, m->body, m->mh);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}else // All other "multipart" MimeTypes: handle as "multipart/mixed":
|
|
|
|
|
}
|
|
|
|
|
vm.erase(mrel); // don't handle that part as a remaining attachment
|
|
|
|
|
first_html = vm.end(); // to avoid to add it to msg->longmsg_formatted again
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for(auto m = vm.cbegin(); m != vm.cend(); ++m)
|
|
|
|
|
{
|
|
|
|
|
if(m != first_html && m!=first_text)
|
|
|
|
|
{
|
|
|
|
|
add_attachment(msg, m->body, m->mh);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void handle_multipart(message* msg, const MimeHeaders& mh, MultipartMessage& mm)
|
|
|
|
|
{
|
|
|
|
|
for(const auto& m : mm)
|
|
|
|
|
{
|
|
|
|
|
LOG << "####\n" << m << "\n";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(mh.subtype == "alternative")
|
|
|
|
|
{
|
|
|
|
|
handle_multipart_alternative(msg, mm);
|
|
|
|
|
}else // All other "multipart" MimeTypes: handle as "multipart/mixed":
|
|
|
|
|
{
|
|
|
|
|
auto main_content = msg->longmsg ? mm.cend() : std::find_if(mm.cbegin(), mm.cend(), has_mimetype("text/plain") );
|
|
|
|
|
if(main_content != mm.cend())
|
|
|
|
|
{
|
|
|
|
|
// the first "text/plain" part is handeld specially:
|
|
|
|
|
const sv mc_charset = header_value( main_content->mh.tparams, "charset" );
|
|
|
|
|
msg->longmsg = create_string(main_content->body, mc_charset, main_content->mh.decoder );
|
|
|
|
|
}else{
|
|
|
|
|
// stange mailer that send HTML body, no plaintext body:
|
|
|
|
|
main_content = msg->longmsg_formatted ? mm.cend() : std::find_if(mm.cbegin(), mm.cend(), has_mimetype("text/html") );
|
|
|
|
|
if(main_content != mm.cend())
|
|
|
|
|
{
|
|
|
|
|
for(const auto& m : vm)
|
|
|
|
|
{
|
|
|
|
|
add_attachment(msg, m.body, m.mh);
|
|
|
|
|
}
|
|
|
|
|
const sv mc_charset = header_value( main_content->mh.tparams, "charset" );
|
|
|
|
|
msg->longmsg_formatted = create_string(main_content->body, mc_charset, main_content->mh.decoder );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}else if(mh.type == "message")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for(auto q=mm.cbegin(); q!=mm.cend(); ++q)
|
|
|
|
|
{
|
|
|
|
|
// TODO: What shall I do with this MimeType?
|
|
|
|
|
add_attachment(msg, body, mh);
|
|
|
|
|
if(q != main_content)
|
|
|
|
|
add_attachment(msg, q->body, q->mh);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void handle_mime(message* msg, const MimeHeaders& mh, const BodyLines& body)
|
|
|
|
|
{
|
|
|
|
|
if(mh.type == "text")
|
|
|
|
|
{
|
|
|
|
|
const sv charset = header_value( mh.tparams, "charset" );
|
|
|
|
|
LOG << "\t Content-Type: " << mh.mime_type() << ", mh: " << mh << "\n";
|
|
|
|
|
if(mh.subtype == "plain" && msg->longmsg==nullptr)
|
|
|
|
|
{
|
|
|
|
|
// put it in msg->longmsg
|
|
|
|
|
msg->longmsg = create_string(body, charset, mh.decoder);
|
|
|
|
|
}else if(mh.subtype=="html" && msg->longmsg_formatted==nullptr)
|
|
|
|
|
{
|
|
|
|
|
// put it in msg->longmsg_formatted
|
|
|
|
|
msg->longmsg_formatted = create_string(body, charset, mh.decoder);
|
|
|
|
|
}else{
|
|
|
|
|
// all other MIME types
|
|
|
|
|
// add it as attachment
|
|
|
|
|
add_attachment(msg, body, mh);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}else if(mh.type == "multipart")
|
|
|
|
|
{
|
|
|
|
|
const sv boundary = header_value(mh.tparams, "boundary");
|
|
|
|
|
MultipartMessage mm = parse_multipart( body, boundary );
|
|
|
|
|
LOG << "MULTIPART/" << mh.subtype << ": " << mm.size() << " parts. Boundary = “" << boundary << "” :\n";
|
|
|
|
|
handle_multipart(msg, mh, mm);
|
|
|
|
|
}else if(mh.type == "message")
|
|
|
|
|
{
|
|
|
|
|
// TODO: What shall I do with this MimeType?
|
|
|
|
|
add_attachment(msg, body, mh);
|
|
|
|
|
}else{
|
|
|
|
|
// all other MIME types
|
|
|
|
|
add_attachment(msg, body, mh);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// parses the header and fill the parts in msg
|
|
|
|
|
void parse_body(message* msg, const HeaderSection& headers, const BodyLines& body)
|
|
|
|
|
{
|
|
|
|
|
const std::string mime_version = header_value(headers, "mime-version").to_string();
|
|
|
|
|
LOG << "ParseBody: " << body.size() << " body lines.\n";
|
|
|
|
|
|
|
|
|
|
if( mime_version == "1.0" ) // TODO: According to RFC 2048 there can be comments in the header field value. -.-
|
|
|
|
|
{
|
|
|
|
|
MimeHeaders mh(headers);
|
|
|
|
|
handle_mime(msg, mh, body);
|
|
|
|
|
}else{ // Non-MIME mail
|
|
|
|
|
LOG << "<<< NO_MIME_MAIL >>> " << body.size() << " body lines.\n";
|
|
|
|
|
sv combined_body = combineLines(body);
|
|
|
|
|
if(isUtf8(combined_body.data(), combined_body.data()+combined_body.size()) )
|
|
|
|
|
{
|
|
|
|
|
const std::string& nfc_string = toNFC( std::string(combined_body) ); // FIXME: double copy! :-((
|
|
|
|
|
const std::string& nfc_string = toNFC( combined_body ); // FIXME: double copy! :-((
|
|
|
|
|
msg->longmsg = new_string(nfc_string.c_str(), nfc_string.size()); // FIXME: 3rd copy! :-(((
|
|
|
|
|
}else{
|
|
|
|
|
char* pbody = msg->longmsg = new_string(combined_body.data(), combined_body.size());
|
|
|
|
|