Fixed bugs in MHTML code + unit tests.

This commit is contained in:
Vincent Richard 2006-07-13 16:32:39 +00:00
parent edca17af10
commit 9a42c8bca4
11 changed files with 367 additions and 88 deletions

View File

@ -2,6 +2,11 @@
VERSION 0.8.1cvs VERSION 0.8.1cvs
================ ================
2006-07-13 Vincent Richard <vincent@vincent-richard.net>
* Fixed bugs in MHTML code: 'CID' prefix should not be case-sensitive;
fixed detection of parts identified by a 'Content-Location'.
2006-04-23 Vincent Richard <vincent@vincent-richard.net> 2006-04-23 Vincent Richard <vincent@vincent-richard.net>
* Added vmime::net::folder::destroy() to delete folders on IMAP and * Added vmime::net::folder::destroy() to delete folders on IMAP and

View File

@ -342,6 +342,7 @@ libvmimetest_sources = [
'tests/parser/dispositionTest.cpp', 'tests/parser/dispositionTest.cpp',
'tests/parser/encoderTest.cpp', 'tests/parser/encoderTest.cpp',
'tests/parser/headerTest.cpp', 'tests/parser/headerTest.cpp',
'tests/parser/htmlTextPartTest.cpp',
'tests/parser/mailboxTest.cpp', 'tests/parser/mailboxTest.cpp',
'tests/parser/mediaTypeTest.cpp', 'tests/parser/mediaTypeTest.cpp',
'tests/parser/messageIdTest.cpp', 'tests/parser/messageIdTest.cpp',

View File

@ -50,7 +50,7 @@ htmlTextPart::~htmlTextPart()
const mediaType htmlTextPart::getType() const const mediaType htmlTextPart::getType() const
{ {
return (mediaType(mediaTypes::TEXT, mediaTypes::TEXT_HTML)); return mediaType(mediaTypes::TEXT, mediaTypes::TEXT_HTML);
} }
@ -60,14 +60,14 @@ const int htmlTextPart::getPartCount() const
} }
void htmlTextPart::generateIn(bodyPart& /* message */, bodyPart& parent) const void htmlTextPart::generateIn(ref <bodyPart> /* message */, ref <bodyPart> parent) const
{ {
// Plain text // Plain text
if (!m_plainText->isEmpty()) if (!m_plainText->isEmpty())
{ {
// -- Create a new part // -- Create a new part
ref <bodyPart> part = vmime::create <bodyPart>(); ref <bodyPart> part = vmime::create <bodyPart>();
parent.getBody()->appendPart(part); parent->getBody()->appendPart(part);
// -- Set header fields // -- Set header fields
part->getHeader()->ContentType()->setValue part->getHeader()->ContentType()->setValue
@ -96,7 +96,7 @@ void htmlTextPart::generateIn(bodyPart& /* message */, bodyPart& parent) const
{ {
// Create a "multipart/related" body part // Create a "multipart/related" body part
ref <bodyPart> relPart = vmime::create <bodyPart>(); ref <bodyPart> relPart = vmime::create <bodyPart>();
parent.getBody()->appendPart(relPart); parent->getBody()->appendPart(relPart);
relPart->getHeader()->ContentType()-> relPart->getHeader()->ContentType()->
setValue(mediaType(mediaTypes::MULTIPART, mediaTypes::MULTIPART_RELATED)); setValue(mediaType(mediaTypes::MULTIPART, mediaTypes::MULTIPART_RELATED));
@ -128,7 +128,7 @@ void htmlTextPart::generateIn(bodyPart& /* message */, bodyPart& parent) const
else else
{ {
// Add the HTML part into the parent part // Add the HTML part into the parent part
parent.getBody()->appendPart(htmlPart); parent->getBody()->appendPart(htmlPart);
} }
} }
@ -140,6 +140,8 @@ void htmlTextPart::findEmbeddedParts(const bodyPart& part,
{ {
ref <const bodyPart> p = part.getBody()->getPartAt(i); ref <const bodyPart> p = part.getBody()->getPartAt(i);
// For a part to be an embedded object, it must have a
// Content-Id field or a Content-Location field.
try try
{ {
p->getHeader()->findField(fields::CONTENT_ID); p->getHeader()->findField(fields::CONTENT_ID);
@ -148,17 +150,16 @@ void htmlTextPart::findEmbeddedParts(const bodyPart& part,
catch (exceptions::no_such_field) catch (exceptions::no_such_field)
{ {
// No "Content-id" field. // No "Content-id" field.
// Maybe there is a "Content-Location" field... }
try try
{ {
p->getHeader()->findField(fields::CONTENT_ID); p->getHeader()->findField(fields::CONTENT_LOCATION);
locParts.push_back(p); locParts.push_back(p);
} }
catch (exceptions::no_such_field) catch (exceptions::no_such_field)
{ {
// No "Content-Location" field. // No "Content-Location" field.
// Cannot be an embedded object since it cannot be referenced in HTML text.
}
} }
findEmbeddedParts(*p, cidParts, locParts); findEmbeddedParts(*p, cidParts, locParts);
@ -168,6 +169,11 @@ void htmlTextPart::findEmbeddedParts(const bodyPart& part,
void htmlTextPart::addEmbeddedObject(const bodyPart& part, const string& id) void htmlTextPart::addEmbeddedObject(const bodyPart& part, const string& id)
{ {
// The object may already exists. This can happen if an object is
// identified by both a Content-Id and a Content-Location. In this
// case, there will be two embedded objects with two different IDs
// but referencing the same content.
mediaType type; mediaType type;
try try
@ -186,28 +192,28 @@ void htmlTextPart::addEmbeddedObject(const bodyPart& part, const string& id)
} }
void htmlTextPart::parse(const bodyPart& message, const bodyPart& parent, const bodyPart& textPart) void htmlTextPart::parse(ref <const bodyPart> message, ref <const bodyPart> parent, ref <const bodyPart> textPart)
{ {
// Search for possible embedded objects in the _whole_ message. // Search for possible embedded objects in the _whole_ message.
std::vector <ref <const bodyPart> > cidParts; std::vector <ref <const bodyPart> > cidParts;
std::vector <ref <const bodyPart> > locParts; std::vector <ref <const bodyPart> > locParts;
findEmbeddedParts(message, cidParts, locParts); findEmbeddedParts(*message, cidParts, locParts);
// Extract HTML text // Extract HTML text
std::ostringstream oss; std::ostringstream oss;
utility::outputStreamAdapter adapter(oss); utility::outputStreamAdapter adapter(oss);
textPart.getBody()->getContents()->extract(adapter); textPart->getBody()->getContents()->extract(adapter);
const string data = oss.str(); const string data = oss.str();
m_text = textPart.getBody()->getContents()->clone(); m_text = textPart->getBody()->getContents()->clone();
try try
{ {
const ref <const contentTypeField> ctf = const ref <const contentTypeField> ctf =
textPart.getHeader()->findField(fields::CONTENT_TYPE).dynamicCast <contentTypeField>(); textPart->getHeader()->findField(fields::CONTENT_TYPE).dynamicCast <contentTypeField>();
m_charset = ctf->getCharset(); m_charset = ctf->getCharset();
} }
@ -229,13 +235,12 @@ void htmlTextPart::parse(const bodyPart& message, const bodyPart& parent, const
const messageId mid = *midField->getValue().dynamicCast <const messageId>(); const messageId mid = *midField->getValue().dynamicCast <const messageId>();
const string searchFor("CID:" + mid.getId()); if (data.find("CID:" + mid.getId()) != string::npos ||
data.find("cid:" + mid.getId()) != string::npos)
if (data.find(searchFor) != string::npos)
{ {
// This part is referenced in the HTML text. // This part is referenced in the HTML text.
// Add it to the embedded object list. // Add it to the embedded object list.
addEmbeddedObject(**p, "CID:" + mid.getId()); addEmbeddedObject(**p, mid.getId());
} }
} }
@ -256,7 +261,7 @@ void htmlTextPart::parse(const bodyPart& message, const bodyPart& parent, const
} }
// Extract plain text, if any. // Extract plain text, if any.
if (!findPlainTextPart(message, parent, textPart)) if (!findPlainTextPart(*message, *parent, *textPart))
{ {
m_plainText = vmime::create <emptyContentHandler>(); m_plainText = vmime::create <emptyContentHandler>();
} }
@ -321,7 +326,7 @@ bool htmlTextPart::findPlainTextPart(const bodyPart& part, const bodyPart& paren
// If we don't have found the plain text part here, it means that // 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 // it does not exists (the MUA which built this message probably
// did not include it...). // did not include it...).
return (found); return found;
} }
} }
} }
@ -337,13 +342,13 @@ bool htmlTextPart::findPlainTextPart(const bodyPart& part, const bodyPart& paren
found = findPlainTextPart(*part.getBody()->getPartAt(i), parent, textPart); found = findPlainTextPart(*part.getBody()->getPartAt(i), parent, textPart);
} }
return (found); return found;
} }
const charset& htmlTextPart::getCharset() const const charset& htmlTextPart::getCharset() const
{ {
return (m_charset); return m_charset;
} }
@ -355,7 +360,7 @@ void htmlTextPart::setCharset(const charset& ch)
const ref <const contentHandler> htmlTextPart::getPlainText() const const ref <const contentHandler> htmlTextPart::getPlainText() const
{ {
return (m_plainText); return m_plainText;
} }
@ -367,7 +372,7 @@ void htmlTextPart::setPlainText(ref <contentHandler> plainText)
const ref <const contentHandler> htmlTextPart::getText() const const ref <const contentHandler> htmlTextPart::getText() const
{ {
return (m_text); return m_text;
} }
@ -379,39 +384,43 @@ void htmlTextPart::setText(ref <contentHandler> text)
const int htmlTextPart::getObjectCount() const const int htmlTextPart::getObjectCount() const
{ {
return (m_objects.size()); return m_objects.size();
} }
const ref <const htmlTextPart::embeddedObject> htmlTextPart::getObjectAt(const int pos) const const ref <const htmlTextPart::embeddedObject> htmlTextPart::getObjectAt(const int pos) const
{ {
return (m_objects[pos]); return m_objects[pos];
} }
const ref <const htmlTextPart::embeddedObject> htmlTextPart::findObject(const string& id) const const ref <const htmlTextPart::embeddedObject> htmlTextPart::findObject(const string& id_) const
{ {
const string id = cleanId(id_);
for (std::vector <ref <embeddedObject> >::const_iterator o = m_objects.begin() ; for (std::vector <ref <embeddedObject> >::const_iterator o = m_objects.begin() ;
o != m_objects.end() ; ++o) o != m_objects.end() ; ++o)
{ {
if ((*o)->getId() == id) if ((*o)->getId() == id)
return (*o); return *o;
} }
throw exceptions::no_object_found(); throw exceptions::no_object_found();
} }
const bool htmlTextPart::hasObject(const string& id) const const bool htmlTextPart::hasObject(const string& id_) const
{ {
const string id = cleanId(id_);
for (std::vector <ref <embeddedObject> >::const_iterator o = m_objects.begin() ; for (std::vector <ref <embeddedObject> >::const_iterator o = m_objects.begin() ;
o != m_objects.end() ; ++o) o != m_objects.end() ; ++o)
{ {
if ((*o)->getId() == id) if ((*o)->getId() == id)
return (true); return true;
} }
return (false); return false;
} }
@ -419,24 +428,42 @@ const string htmlTextPart::addObject(ref <contentHandler> data,
const vmime::encoding& enc, const mediaType& type) const vmime::encoding& enc, const mediaType& type)
{ {
const messageId mid(messageId::generateId()); const messageId mid(messageId::generateId());
const string id = "CID:" + mid.getId(); const string id = mid.getId();
m_objects.push_back(vmime::create <embeddedObject>(data, enc, id, type)); m_objects.push_back(vmime::create <embeddedObject>(data, enc, id, type));
return (id); return "CID:" + id;
} }
const string htmlTextPart::addObject(ref <contentHandler> data, const mediaType& type) const string htmlTextPart::addObject(ref <contentHandler> data, const mediaType& type)
{ {
return (addObject(data, encoding::decide(data), type)); return addObject(data, encoding::decide(data), type);
} }
const string htmlTextPart::addObject(const string& data, const mediaType& type) const string htmlTextPart::addObject(const string& data, const mediaType& type)
{ {
ref <stringContentHandler> cts = vmime::create <stringContentHandler>(data); ref <stringContentHandler> cts = vmime::create <stringContentHandler>(data);
return (addObject(cts, encoding::decide(cts), type)); return addObject(cts, encoding::decide(cts), type);
}
// static
const string htmlTextPart::cleanId(const string& id)
{
if (id.length() >= 4 &&
(id[0] == 'c' || id[0] == 'C') &&
(id[1] == 'i' || id[1] == 'I') &&
(id[2] == 'd' || id[2] == 'D') &&
id[3] == ':')
{
return id.substr(4);
}
else
{
return id;
}
} }
@ -456,25 +483,25 @@ htmlTextPart::embeddedObject::embeddedObject
const ref <const contentHandler> htmlTextPart::embeddedObject::getData() const const ref <const contentHandler> htmlTextPart::embeddedObject::getData() const
{ {
return (m_data); return m_data;
} }
const vmime::encoding& htmlTextPart::embeddedObject::getEncoding() const const vmime::encoding& htmlTextPart::embeddedObject::getEncoding() const
{ {
return (m_encoding); return m_encoding;
} }
const string& htmlTextPart::embeddedObject::getId() const const string& htmlTextPart::embeddedObject::getId() const
{ {
return (m_id); return m_id;
} }
const mediaType& htmlTextPart::embeddedObject::getType() const const mediaType& htmlTextPart::embeddedObject::getType() const
{ {
return (m_type); return m_type;
} }

View File

@ -105,12 +105,12 @@ ref <message> messageBuilder::construct() const
// Generate the text parts into this sub-part (normally, this // Generate the text parts into this sub-part (normally, this
// sub-part will have the "multipart/alternative" content-type...) // sub-part will have the "multipart/alternative" content-type...)
m_textPart->generateIn(*msg, *subPart); m_textPart->generateIn(msg, subPart);
} }
else else
{ {
// Generate the text part(s) directly into the message // Generate the text part(s) directly into the message
m_textPart->generateIn(*msg, *msg); m_textPart->generateIn(msg, msg);
// If any attachment, set message content-type to "multipart/mixed" // If any attachment, set message content-type to "multipart/mixed"
if (!m_attach.empty()) if (!m_attach.empty())

View File

@ -100,7 +100,7 @@ void messageParser::parse(ref <const message> msg)
findAttachments(msg); findAttachments(msg);
// Text parts // Text parts
findTextParts(*msg, *msg); findTextParts(msg, msg);
} }
@ -110,11 +110,11 @@ void messageParser::findAttachments(ref <const message> msg)
} }
void messageParser::findTextParts(const bodyPart& msg, const bodyPart& part) void messageParser::findTextParts(ref <const bodyPart> msg, ref <const bodyPart> part)
{ {
// Handle the case in which the message is not multipart: if the body part is // Handle the case in which the message is not multipart: if the body part is
// "text/*", take this part. // "text/*", take this part.
if (part.getBody()->getPartCount() == 0) if (part->getBody()->getPartCount() == 0)
{ {
mediaType type(mediaTypes::TEXT, mediaTypes::TEXT_PLAIN); mediaType type(mediaTypes::TEXT, mediaTypes::TEXT_PLAIN);
bool accept = false; bool accept = false;
@ -122,7 +122,7 @@ void messageParser::findTextParts(const bodyPart& msg, const bodyPart& part)
try try
{ {
const contentTypeField& ctf = dynamic_cast<contentTypeField&> const contentTypeField& ctf = dynamic_cast<contentTypeField&>
(*msg.getHeader()->findField(fields::CONTENT_TYPE)); (*msg->getHeader()->findField(fields::CONTENT_TYPE));
const mediaType ctfType = const mediaType ctfType =
*ctf.getValue().dynamicCast <const mediaType>(); *ctf.getValue().dynamicCast <const mediaType>();
@ -155,7 +155,7 @@ void messageParser::findTextParts(const bodyPart& msg, const bodyPart& part)
} }
bool messageParser::findSubTextParts(const bodyPart& msg, const bodyPart& part) bool messageParser::findSubTextParts(ref <const bodyPart> msg, ref <const bodyPart> part)
{ {
// In general, all the text parts are contained in parallel in the same // In general, all the text parts are contained in parallel in the same
// parent part (or message). // parent part (or message).
@ -164,9 +164,9 @@ bool messageParser::findSubTextParts(const bodyPart& msg, const bodyPart& part)
std::vector <ref <const bodyPart> > textParts; std::vector <ref <const bodyPart> > textParts;
for (int i = 0 ; i < part.getBody()->getPartCount() ; ++i) for (int i = 0 ; i < part->getBody()->getPartCount() ; ++i)
{ {
const ref <const bodyPart> p = part.getBody()->getPartAt(i); const ref <const bodyPart> p = part->getBody()->getPartAt(i);
try try
{ {
@ -200,7 +200,7 @@ bool messageParser::findSubTextParts(const bodyPart& msg, const bodyPart& part)
try try
{ {
ref <textPart> txtPart = textPartFactory::getInstance()->create(type); ref <textPart> txtPart = textPartFactory::getInstance()->create(type);
txtPart->parse(msg, part, **p); txtPart->parse(msg, part, *p);
m_textParts.push_back(txtPart); m_textParts.push_back(txtPart);
} }
@ -209,22 +209,17 @@ bool messageParser::findSubTextParts(const bodyPart& msg, const bodyPart& part)
// Content-type not recognized. // Content-type not recognized.
} }
} }
//return true;
} }
//else
{
bool found = false; bool found = false;
for (int i = 0 ; !found && (i < part.getBody()->getPartCount()) ; ++i) for (int i = 0 ; !found && (i < part->getBody()->getPartCount()) ; ++i)
{ {
found = findSubTextParts(msg, *part.getBody()->getPartAt(i)); found = findSubTextParts(msg, part->getBody()->getPartAt(i));
} }
return found; return found;
} }
}
const mailbox& messageParser::getExpeditor() const const mailbox& messageParser::getExpeditor() const

View File

@ -57,11 +57,11 @@ const int plainTextPart::getPartCount() const
} }
void plainTextPart::generateIn(bodyPart& /* message */, bodyPart& parent) const void plainTextPart::generateIn(ref <bodyPart> /* message */, ref <bodyPart> parent) const
{ {
// Create a new part // Create a new part
ref <bodyPart> part = vmime::create <bodyPart>(); ref <bodyPart> part = vmime::create <bodyPart>();
parent.getBody()->appendPart(part); parent->getBody()->appendPart(part);
// Set header fields // Set header fields
part->getHeader()->ContentType()->setValue(mediaType(mediaTypes::TEXT, mediaTypes::TEXT_PLAIN)); part->getHeader()->ContentType()->setValue(mediaType(mediaTypes::TEXT, mediaTypes::TEXT_PLAIN));
@ -73,15 +73,15 @@ void plainTextPart::generateIn(bodyPart& /* message */, bodyPart& parent) const
} }
void plainTextPart::parse(const bodyPart& /* message */, void plainTextPart::parse(ref <const bodyPart> /* message */,
const bodyPart& /* parent */, const bodyPart& textPart) ref <const bodyPart> /* parent */, ref <const bodyPart> textPart)
{ {
m_text = textPart.getBody()->getContents()->clone().dynamicCast <contentHandler>(); m_text = textPart->getBody()->getContents()->clone().dynamicCast <contentHandler>();
try try
{ {
const contentTypeField& ctf = dynamic_cast<contentTypeField&> const contentTypeField& ctf = dynamic_cast<contentTypeField&>
(*textPart.getHeader()->findField(fields::CONTENT_TYPE)); (*textPart->getHeader()->findField(fields::CONTENT_TYPE));
m_charset = ctf.getCharset(); m_charset = ctf.getCharset();
} }

View File

@ -0,0 +1,226 @@
//
// VMime library (http://www.vmime.org)
// Copyright (C) 2002-2006 Vincent Richard <vincent@vincent-richard.net>
//
// 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.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// Linking this library statically or dynamically with other modules is making
// a combined work based on this library. Thus, the terms and conditions of
// the GNU General Public License cover the whole combination.
//
#include "tests/testUtils.hpp"
#include "vmime/htmlTextPart.hpp"
#define VMIME_TEST_SUITE htmlTextPartTest
#define VMIME_TEST_SUITE_MODULE "Parser"
VMIME_TEST_SUITE_BEGIN
VMIME_TEST_LIST_BEGIN
VMIME_TEST(testParseText)
VMIME_TEST(testParseEmbeddedObjectsCID)
VMIME_TEST(testParseEmbeddedObjectsLocation)
VMIME_TEST_LIST_END
static const vmime::string extractContent
(vmime::ref <const vmime::contentHandler> cth)
{
std::ostringstream oss;
vmime::utility::outputStreamAdapter osa(oss);
cth->extract(osa);
return oss.str();
}
void testParseText()
{
const vmime::string msgString = ""
"MIME-Version: 1.0\r\n"
"Content-Type: multipart/alternative; boundary=\"LEVEL1\"\r\n"
"\r\n"
"--LEVEL1\r\n"
"Content-Type: text/plain; charset=\"x-ch1\"\r\n"
"\r\n"
"Plain text part\r\n"
"--LEVEL1\r\n"
"Content-Type: multipart/related; boundary=\"LEVEL2\"\r\n"
"\r\n"
"--LEVEL2\r\n"
"Content-Type: text/html; charset=\"x-ch2\"\r\n"
"\r\n"
"<img src=\"cid:image@test\"/>\r\n"
"--LEVEL2\r\n"
"Content-Type: image/png; name=\"image.png\"\r\n"
"Content-Disposition: inline; filename=\"image.png\"\r\n"
"Content-ID: <image@test>\r\n"
"\r\n"
"Image\r\n"
"--LEVEL2--\r\n"
"\r\n"
"--LEVEL1--\r\n"
"";
vmime::ref <vmime::message> msg = vmime::create <vmime::message>();
msg->parse(msgString);
vmime::htmlTextPart htmlPart;
htmlPart.parse(msg, msg->getBody()->getPartAt(1),
msg->getBody()->getPartAt(1)->getBody()->getPartAt(0));
VASSERT_EQ("plain", "Plain text part", extractContent(htmlPart.getPlainText()));
VASSERT_EQ("html", "<img src=\"cid:image@test\"/>", extractContent(htmlPart.getText()));
// Should return the charset of the HTML part
VASSERT_EQ("charset", "x-ch2", htmlPart.getCharset().generate());
}
/** Test parsing of embedded objects by CID (Content-Id).
*/
void testParseEmbeddedObjectsCID()
{
const vmime::string msgString = ""
"MIME-Version: 1.0\r\n"
"Content-Type: multipart/alternative; boundary=\"LEVEL1\"\r\n"
"\r\n"
"--LEVEL1\r\n"
"Content-Type: text/plain; charset=\"x-ch1\"\r\n"
"\r\n"
"Plain text part\r\n"
"--LEVEL1\r\n"
"Content-Type: multipart/related; boundary=\"LEVEL2\"\r\n"
"\r\n"
"--LEVEL2\r\n" // one embedded object before...
"Content-Type: image/png; name=\"image1.png\"\r\n"
"Content-Disposition: inline; filename=\"image1.png\"\r\n"
"Content-ID: <image1@test>\r\n"
"\r\n"
"Image1\r\n"
"--LEVEL2\r\n" // ...the actual text part...
"Content-Type: text/html; charset=\"x-ch2\"\r\n"
"\r\n"
"<img src=\"cid:image1@test\"/>\r\n"
"<img src=\"CID:image2@test\"/>\r\n"
"--LEVEL2\r\n" // ...and one after
"Content-Type: image/jpeg; name=\"image2.jpg\"\r\n"
"Content-Disposition: inline; filename=\"image2.jpg\"\r\n"
"Content-ID: <image2@test>\r\n"
"\r\n"
"Image2\r\n"
"--LEVEL2--\r\n"
"\r\n"
"--LEVEL1--\r\n"
"";
vmime::ref <vmime::message> msg = vmime::create <vmime::message>();
msg->parse(msgString);
vmime::htmlTextPart htmlPart;
htmlPart.parse(msg, msg->getBody()->getPartAt(1),
msg->getBody()->getPartAt(1)->getBody()->getPartAt(1));
// Two embedded objects should be found.
// BUGFIX: "CID:" prefix is not case-sensitive.
VASSERT_EQ("count", 2, htmlPart.getObjectCount());
// Ensure the right objects have been found.
VASSERT_EQ("has-obj1", true, htmlPart.hasObject("image1@test"));
VASSERT_EQ("has-obj2", true, htmlPart.hasObject("image2@test"));
// hasObject() should also work with prefixes
VASSERT_EQ("has-obj1-pre", true, htmlPart.hasObject("CID:image1@test"));
VASSERT_EQ("has-obj2-pre", true, htmlPart.hasObject("cid:image2@test"));
// Check data in objects
vmime::ref <const vmime::htmlTextPart::embeddedObject> obj;
obj = htmlPart.findObject("image1@test");
VASSERT_EQ("id-obj1", "image1@test", obj->getId());
VASSERT_EQ("data-obj1", "Image1", extractContent(obj->getData()));
VASSERT_EQ("type-obj1", "image/png", obj->getType().generate());
obj = htmlPart.findObject("image2@test");
VASSERT_EQ("id-obj2", "image2@test", obj->getId());
VASSERT_EQ("data-obj2", "Image2", extractContent(obj->getData()));
VASSERT_EQ("type-obj2", "image/jpeg", obj->getType().generate());
}
/** Test parsing of embedded objects by location.
*/
void testParseEmbeddedObjectsLocation()
{
const vmime::string msgString = ""
"MIME-Version: 1.0\r\n"
"Content-Type: multipart/alternative; boundary=\"LEVEL1\"\r\n"
"\r\n"
"--LEVEL1\r\n"
"Content-Type: text/plain; charset=\"x-ch1\"\r\n"
"\r\n"
"Plain text part\r\n"
"--LEVEL1\r\n"
"Content-Type: multipart/related; boundary=\"LEVEL2\"\r\n"
"\r\n"
"--LEVEL2\r\n"
"Content-Type: image/png; name=\"image1.png\"\r\n"
"Content-Location: http://www.vmime.org/test/image1.png\r\n"
"Content-Disposition: inline; filename=\"image1.png\"\r\n"
"Content-Id: <image1@test>\r\n"
"\r\n"
"Image1\r\n"
"--LEVEL2\r\n"
"Content-Type: text/html; charset=\"x-ch2\"\r\n"
"\r\n"
"<img src=\"http://www.vmime.org/test/image1.png\"/>\r\n"
"--LEVEL2--\r\n"
"\r\n"
"--LEVEL1--\r\n"
"";
vmime::ref <vmime::message> msg = vmime::create <vmime::message>();
msg->parse(msgString);
vmime::htmlTextPart htmlPart;
htmlPart.parse(msg, msg->getBody()->getPartAt(1),
msg->getBody()->getPartAt(1)->getBody()->getPartAt(1));
// Only one embedded object
VASSERT_EQ("count", 1, htmlPart.getObjectCount());
// Should work only with Content-Location as the Content-Id is
// not referenced in the HTML contents
VASSERT_EQ("has-obj-loc", true, htmlPart.hasObject("http://www.vmime.org/test/image1.png"));
VASSERT_EQ("has-obj-cid", false, htmlPart.hasObject("image1@test"));
// Check data
vmime::ref <const vmime::htmlTextPart::embeddedObject> obj;
obj = htmlPart.findObject("http://www.vmime.org/test/image1.png");
VASSERT_EQ("id-obj", "http://www.vmime.org/test/image1.png", obj->getId());
VASSERT_EQ("data-obj", "Image1", extractContent(obj->getData()));
VASSERT_EQ("type-obj", "image/png", obj->getType().generate());
}
// TODO: test generation of text parts
VMIME_TEST_SUITE_END

View File

@ -131,6 +131,8 @@ public:
const ref <const embeddedObject> getObjectAt(const int pos) const; const ref <const embeddedObject> getObjectAt(const int pos) const;
/** Embed an object and returns a string which identifies it. /** Embed an object and returns a string which identifies it.
* The returned identifier is suitable for use in the 'src' attribute
* of an <img> tag.
* *
* \deprecated Use the addObject() methods which take a 'contentHandler' * \deprecated Use the addObject() methods which take a 'contentHandler'
* parameter type instead. * parameter type instead.
@ -143,6 +145,8 @@ public:
const string addObject(const string& data, const mediaType& type); const string addObject(const string& data, const mediaType& type);
/** Embed an object and returns a string which identifies it. /** Embed an object and returns a string which identifies it.
* The returned identifier is suitable for use in the 'src' attribute
* of an <img> tag.
* *
* @param data object data * @param data object data
* @param type data type * @param type data type
@ -152,6 +156,8 @@ public:
const string addObject(ref <contentHandler> data, const mediaType& type); const string addObject(ref <contentHandler> data, const mediaType& type);
/** Embed an object and returns a string which identifies it. /** Embed an object and returns a string which identifies it.
* The returned identifier is suitable for use in the 'src' attribute
* of an <img> tag.
* *
* @param data object data * @param data object data
* @param enc data encoding * @param enc data encoding
@ -161,6 +167,12 @@ public:
*/ */
const string addObject(ref <contentHandler> data, const encoding& enc, const mediaType& type); const string addObject(ref <contentHandler> data, const encoding& enc, const mediaType& type);
const int getPartCount() const;
void generateIn(ref <bodyPart> message, ref <bodyPart> parent) const;
void parse(ref <const bodyPart> message, ref <const bodyPart> parent, ref <const bodyPart> textPart);
private: private:
ref <contentHandler> m_plainText; ref <contentHandler> m_plainText;
@ -174,10 +186,7 @@ private:
bool findPlainTextPart(const bodyPart& part, const bodyPart& parent, const bodyPart& textPart); bool findPlainTextPart(const bodyPart& part, const bodyPart& parent, const bodyPart& textPart);
const int getPartCount() const; static const string cleanId(const string& id);
void generateIn(bodyPart& message, bodyPart& parent) const;
void parse(const bodyPart& message, const bodyPart& parent, const bodyPart& textPart);
}; };

View File

@ -148,8 +148,8 @@ private:
void findAttachments(ref <const message> msg); void findAttachments(ref <const message> msg);
void findTextParts(const bodyPart& msg, const bodyPart& part); void findTextParts(ref <const bodyPart> msg, ref <const bodyPart> part);
bool findSubTextParts(const bodyPart& msg, const bodyPart& part); bool findSubTextParts(ref <const bodyPart> msg, ref <const bodyPart> part);
}; };

View File

@ -50,15 +50,15 @@ public:
const ref <const contentHandler> getText() const; const ref <const contentHandler> getText() const;
void setText(ref <contentHandler> text); void setText(ref <contentHandler> text);
const int getPartCount() const;
void generateIn(ref <bodyPart> message, ref <bodyPart> parent) const;
void parse(ref <const bodyPart> message, ref <const bodyPart> parent, ref <const bodyPart> textPart);
private: private:
ref <contentHandler> m_text; ref <contentHandler> m_text;
charset m_charset; charset m_charset;
const int getPartCount() const;
void generateIn(bodyPart& message, bodyPart& parent) const;
void parse(const bodyPart& message, const bodyPart& parent, const bodyPart& textPart);
}; };

View File

@ -81,12 +81,28 @@ public:
*/ */
virtual void setText(ref <contentHandler> text) = 0; virtual void setText(ref <contentHandler> text) = 0;
protected: /** Return the actual body parts this text part is composed of.
* For example, HTML parts are composed of two parts: one "text/html"
* part, and the plain text part "text/plain".
*
* @return number of body parts
*/
virtual const int getPartCount() const = 0; virtual const int getPartCount() const = 0;
virtual void generateIn(bodyPart& message, bodyPart& parent) const = 0; /** Generate the text part(s) into the specified message.
virtual void parse(const bodyPart& message, const bodyPart& parent, const bodyPart& textPart) = 0; *
* @param message the message
* @param parent body part into which generate this part
*/
virtual void generateIn(ref <bodyPart> message, ref <bodyPart> parent) const = 0;
/** Parse the text part(s) from the specified message.
*
* @param message message containing the text part
* @param parent part containing the text part
* @param textPart actual text part
*/
virtual void parse(ref <const bodyPart> message, ref <const bodyPart> parent, ref <const bodyPart> textPart) = 0;
}; };