Fixed bugs in MHTML code + unit tests.
This commit is contained in:
parent
edca17af10
commit
9a42c8bca4
@ -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
|
||||||
|
@ -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',
|
||||||
|
@ -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);
|
{
|
||||||
locParts.push_back(p);
|
p->getHeader()->findField(fields::CONTENT_LOCATION);
|
||||||
}
|
locParts.push_back(p);
|
||||||
catch (exceptions::no_such_field)
|
}
|
||||||
{
|
catch (exceptions::no_such_field)
|
||||||
// No "Content-Location" field.
|
{
|
||||||
// Cannot be an embedded object since it cannot be referenced in HTML text.
|
// No "Content-Location" field.
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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())
|
||||||
|
@ -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,21 +209,16 @@ bool messageParser::findSubTextParts(const bodyPart& msg, const bodyPart& part)
|
|||||||
// Content-type not recognized.
|
// Content-type not recognized.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//else
|
bool found = false;
|
||||||
|
|
||||||
|
for (int i = 0 ; !found && (i < part->getBody()->getPartCount()) ; ++i)
|
||||||
{
|
{
|
||||||
bool found = false;
|
found = findSubTextParts(msg, part->getBody()->getPartAt(i));
|
||||||
|
|
||||||
for (int i = 0 ; !found && (i < part.getBody()->getPartCount()) ; ++i)
|
|
||||||
{
|
|
||||||
found = findSubTextParts(msg, *part.getBody()->getPartAt(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
return found;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
226
tests/parser/htmlTextPartTest.cpp
Normal file
226
tests/parser/htmlTextPartTest.cpp
Normal 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
|
||||||
|
|
@ -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);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -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);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user