// // VMime library (http://vmime.sourceforge.net) // Copyright (C) 2002-2004 Vincent Richard // // 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. // #include "htmlTextPart.hpp" #include "exception.hpp" namespace vmime { htmlTextPart::~htmlTextPart() { } const mediaType htmlTextPart::type() const { return (mediaType(mediaTypes::TEXT, mediaTypes::TEXT_HTML)); } const int htmlTextPart::getPartCount() const { return (m_plainText.empty() ? 1 : 2); } void htmlTextPart::generateIn(bodyPart& /* message */, bodyPart& parent) const { // Plain text if (!m_plainText.empty()) { // -- Create a new part bodyPart* part = new bodyPart(); parent.body().parts.append(part); // -- Set header fields part->header().fields.ContentType() = mediaType(mediaTypes::TEXT, mediaTypes::TEXT_PLAIN); part->header().fields.ContentType().charset() = m_charset; part->header().fields.ContentTransferEncoding() = encoding(encodingTypes::QUOTED_PRINTABLE); // -- Set contents part->body().contents() = m_plainText; } // HTML text // -- Create a new part bodyPart* htmlPart = new bodyPart(); // -- Set header fields htmlPart->header().fields.ContentType() = mediaType(mediaTypes::TEXT, mediaTypes::TEXT_HTML); htmlPart->header().fields.ContentType().charset() = m_charset; htmlPart->header().fields.ContentTransferEncoding() = encoding(encodingTypes::QUOTED_PRINTABLE); // -- Set contents htmlPart->body().contents() = m_text; // Handle the case we have embedded objects if (!embeddedObjects.empty()) { // Create a "multipart/related" body part bodyPart* relPart = new bodyPart(); parent.body().parts.append(relPart); relPart->header().fields.ContentType() = mediaType (mediaTypes::MULTIPART, mediaTypes::MULTIPART_RELATED); // Add the HTML part into this part relPart->body().parts.append(htmlPart); // Also add images into this part for (embeddedObjectsContainer::const_iterator i = embeddedObjects.begin() ; i != embeddedObjects.end() ; ++i) { bodyPart* objPart = new bodyPart(); relPart->body().parts.append(objPart); string id = (*i).id(); if (id.substr(0, 4) == "CID:") id = id.substr(4); objPart->header().fields.ContentType() = (*i).type(); objPart->header().fields.ContentId() = messageId("<" + id + ">"); objPart->header().fields.ContentDisposition() = disposition(dispositionTypes::INLINE); objPart->header().fields.ContentTransferEncoding() = (*i).encoding(); //encoding(encodingTypes::BASE64); objPart->body().contents() = (*i).data(); } } else { // Add the HTML part into the parent part parent.body().parts.append(htmlPart); } } void htmlTextPart::findEmbeddedParts(const bodyPart& part, std::vector & cidParts, std::vector & locParts) { for (body::const_iterator p = part.body().parts.begin() ; p != part.body().parts.end() ; ++p) { try { dynamic_cast((*p).header().fields.find(headerField::ContentId)); cidParts.push_back(&(*p)); } catch (exceptions::no_such_field) { // No "Content-id" field. // Maybe there is a "Content-Location" field... try { dynamic_cast((*p).header().fields.find(headerField::ContentId)); locParts.push_back(&(*p)); } catch (exceptions::no_such_field) { // No "Content-Location" field. // Cannot be an embedded object since it cannot be referenced in HTML text. } } findEmbeddedParts((*p), cidParts, locParts); } } void htmlTextPart::addEmbeddedObject(const bodyPart& part, const string& id) { mediaType type; try { const contentTypeField& ctf = dynamic_cast (part.header().fields.find(headerField::ContentType)); type = ctf.value(); } catch (exceptions::no_such_field) { // No "Content-type" field: assume "application/octet-stream". } embeddedObjects.m_list.push_back(new embeddedObject (part.body().contents(), part.body().encoding(), id, type)); } void htmlTextPart::parse(const bodyPart& message, const bodyPart& parent, const bodyPart& textPart) { // Search for possible embedded objects in the _whole_ message. std::vector cidParts; std::vector locParts; findEmbeddedParts(message, cidParts, locParts); // Extract HTML text std::ostringstream oss; utility::outputStreamAdapter adapter(oss); textPart.body().contents().extract(adapter); const string data = oss.str(); m_text = textPart.body().contents(); try { const contentTypeField& ctf = dynamic_cast (textPart.header().fields.find(headerField::ContentType)); m_charset = ctf.charset(); } catch (exceptions::no_such_field) { // No "Content-type" field. } catch (exceptions::no_such_parameter) { // No "charset" parameter. } // Extract embedded objects. The algorithm is quite simple: for each previously // found inline part, we check if its CID/Location is contained in the HTML text. for (std::vector ::const_iterator p = cidParts.begin() ; p != cidParts.end() ; ++p) { const messageIdField& midField = dynamic_cast ((**p).header().fields.find(headerField::ContentId)); const string searchFor("CID:" + midField.value().id()); if (data.find(searchFor) != string::npos) { // This part is referenced in the HTML text. // Add it to the embedded object list. addEmbeddedObject(**p, "CID:" + midField.value().id()); } } for (std::vector ::const_iterator p = locParts.begin() ; p != locParts.end() ; ++p) { const defaultField& locField = dynamic_cast ((**p).header().fields.find(headerField::ContentLocation)); if (data.find(locField.value()) != string::npos) { // This part is referenced in the HTML text. // Add it to the embedded object list. addEmbeddedObject(**p, locField.value()); } } // Extract plain text, if any. findPlainTextPart(message, parent, textPart); } bool htmlTextPart::findPlainTextPart(const bodyPart& part, const bodyPart& parent, const bodyPart& textPart) { // We search for the nearest "multipart/alternative" part. try { const contentTypeField& ctf = dynamic_cast (part.header().fields.find(headerField::ContentType)); if (ctf.value().type() == mediaTypes::MULTIPART && ctf.value().subType() == mediaTypes::MULTIPART_ALTERNATIVE) { bodyPart const* foundPart = NULL; for (body::const_iterator p = part.body().parts.begin() ; !foundPart && p != part.body().parts.end() ; ++p) { if (&(*p) == &parent || // if "text/html" is in "multipart/related" &(*p) == &textPart) // if not... { foundPart = &(*p); } } if (foundPart) { bool found = false; // Now, search for the alternative plain text part for (body::const_iterator p = part.body().parts.begin() ; !found && p != part.body().parts.end() ; ++p) { try { const contentTypeField& ctf = dynamic_cast ((*p).header().fields.find(headerField::ContentType)); if (ctf.value().type() == mediaTypes::TEXT && ctf.value().subType() == mediaTypes::TEXT_PLAIN) { m_plainText = (*p).body().contents(); found = true; } } catch (exceptions::no_such_field) { // No "Content-type" field. } } // If we don't have found the plain text part here, it means that // it does not exists (the MUA which built this message probably // did not include it...). return (found); } } } catch (exceptions::no_such_field) { // No "Content-type" field. } bool found = false; for (body::const_iterator p = part.body().parts.begin() ; !found && p != part.body().parts.end() ; ++p) { found = findPlainTextPart(*p, parent, textPart); } return (found); } //////////////////////////////// // Embedded objects container // //////////////////////////////// htmlTextPart::embeddedObjectsContainer::~embeddedObjectsContainer() { free_container(m_list); } const htmlTextPart::embeddedObject& htmlTextPart::embeddedObjectsContainer::find(const string& id) const { for (std::vector ::const_iterator o = m_list.begin() ; o != m_list.end() ; ++o) { if ((**o).id() == id) return (**o); } throw exceptions::no_object_found(); } const bool htmlTextPart::embeddedObjectsContainer::has(const string& id) const { for (std::vector ::const_iterator o = m_list.begin() ; o != m_list.end() ; ++o) { if ((**o).id() == id) return (true); } return (false); } const string htmlTextPart::embeddedObjectsContainer::add (const contentHandler& data, const vmime::encoding& enc, const mediaType& type) { const messageId mid(messageId::generateId()); const string id = "CID:" + mid.id(); m_list.push_back(new embeddedObject(data, enc, id, type)); return (id); } const string htmlTextPart::embeddedObjectsContainer::add (const contentHandler& data, const mediaType& type) { return (add(data, encoding::decide(data), type)); } const string htmlTextPart::embeddedObjectsContainer::add (const string& data, const mediaType& type) { return (add(contentHandler(data), encoding::decide(data), type)); } } // vmime