diff --git a/kolabformat/mimeobject.cpp b/kolabformat/mimeobject.cpp index d2eadf1..3b88432 100644 --- a/kolabformat/mimeobject.cpp +++ b/kolabformat/mimeobject.cpp @@ -1,754 +1,758 @@ /* * Copyright (C) 2012 Sofia Balicka * Copyright (C) 2014 Christian Mollekopf * * This program 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 Lesser 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 . */ #include "mimeobject.h" #include "conversion/kcalconversion.h" #include "conversion/kolabconversion.h" #include "conversion/kabcconversion.h" #include "conversion/commonconversion.h" #include "kolabformat/kolabobject.h" #include "kolabformat/xmlobject.h" #include "kolabformat/errorhandler.h" #include "kolabformat/v2helpers.h" #include "mime/mimeutils.h" #include "libkolab-version.h" #include #include #include #include Q_DECLARE_METATYPE(Kolab::Event); Q_DECLARE_METATYPE(Kolab::Todo); Q_DECLARE_METATYPE(Kolab::Journal); Q_DECLARE_METATYPE(Kolab::Contact); Q_DECLARE_METATYPE(Kolab::DistList); Q_DECLARE_METATYPE(Kolab::Note); Q_DECLARE_METATYPE(Kolab::Freebusy); Q_DECLARE_METATYPE(Kolab::Configuration); static inline std::string eventKolabType() { return std::string(KOLAB_TYPE_EVENT); }; static inline std::string todoKolabType() { return std::string(KOLAB_TYPE_TASK); }; static inline std::string journalKolabType() { return std::string(KOLAB_TYPE_JOURNAL); }; static inline std::string contactKolabType() { return std::string(KOLAB_TYPE_CONTACT); }; static inline std::string distlistKolabType() { return std::string(KOLAB_TYPE_DISTLIST); } static inline std::string distlistKolabTypeCompat() { return std::string(KOLAB_TYPE_DISTLIST_V2); } static inline std::string noteKolabType() { return std::string(KOLAB_TYPE_NOTE); } static inline std::string configurationKolabType() { return std::string(KOLAB_TYPE_CONFIGURATION); } static inline std::string dictKolabType() { return std::string(KOLAB_TYPE_DICT); } static inline std::string freebusyKolabType() { return std::string(KOLAB_TYPE_FREEBUSY); } static inline std::string relationKolabType() { return std::string(KOLAB_TYPE_RELATION); } static inline std::string xCalMimeType() { return std::string(MIME_TYPE_XCAL); }; static inline std::string xCardMimeType() { return std::string(MIME_TYPE_XCARD); }; static inline std::string kolabMimeType() { return std::string(MIME_TYPE_KOLAB); }; static std::string getProductId(const std::string &pId) { if (pId.empty()) { return LIBKOLAB_LIB_VERSION_STRING; } return pId + " " + LIBKOLAB_LIB_VERSION_STRING; } namespace Kolab { static Kolab::ObjectType getObjectType(const std::string &type) { if (type == eventKolabType()) { return EventObject; } else if (type == todoKolabType()) { return TodoObject; } else if (type == journalKolabType()) { return JournalObject; } else if (type == contactKolabType()) { return ContactObject; } else if (type == distlistKolabType() || type == distlistKolabTypeCompat()) { return DistlistObject; } else if (type == noteKolabType()) { return NoteObject; } else if (type == freebusyKolabType()) { return FreebusyObject; } else if (boost::contains(type, dictKolabType())) { //Previous versions appended the language to the type return DictionaryConfigurationObject; } else if (type == relationKolabType()) { return RelationConfigurationObject; } Warning() << "Unknown object type: " << type; return Kolab::InvalidObject; } static QByteArray getTypeString(Kolab::ObjectType type) { switch (type) { case EventObject: return KOLAB_TYPE_EVENT; case TodoObject: return KOLAB_TYPE_TASK; case JournalObject: return KOLAB_TYPE_JOURNAL; case FreebusyObject: return KOLAB_TYPE_FREEBUSY; case ContactObject: return KOLAB_TYPE_CONTACT; case DistlistObject: return KOLAB_TYPE_DISTLIST; case NoteObject: return KOLAB_TYPE_NOTE; case DictionaryConfigurationObject: return KOLAB_TYPE_CONFIGURATION; case RelationConfigurationObject: return KOLAB_TYPE_RELATION; default: Critical() << "unknown type "<< type; } return QByteArray(); } static QByteArray getMimeType(Kolab::ObjectType type) { switch (type) { case EventObject: case TodoObject: case JournalObject: case FreebusyObject: return MIME_TYPE_XCAL; case ContactObject: case DistlistObject: return MIME_TYPE_XCARD; case NoteObject: case DictionaryConfigurationObject: case RelationConfigurationObject: return MIME_TYPE_KOLAB; default: Critical() << "unknown type "<< type; } return QByteArray(); } static Kolab::ObjectType detectType(const KMime::Message::Ptr &msg) { Q_FOREACH(const QByteArray &type, Mime::getContentMimeTypeList(msg)) { Kolab::ObjectType t = getObjectType(type.toStdString()); //works for v2 types if (t != InvalidObject) { return t; } } return InvalidObject; } static void printMessageDebugInfo(const KMime::Message::Ptr &msg) { //TODO replace by Debug stream for Mimemessage Debug() << "MessageId: " << msg->messageID()->asUnicodeString(); Debug() << "Subject: " << msg->subject()->asUnicodeString(); // Debug() << msg->encodedContent(); } //@cond PRIVATE class MIMEObject::Private { public: Private() : mObjectType(InvalidObject), mVersion(KolabV3), mOverrideObjectType(InvalidObject), mDoOverrideVersion(false) { } QVariant readKolabV2(const KMime::Message::Ptr &msg, Kolab::ObjectType objectType); QVariant readKolabV3(const KMime::Message::Ptr &msg, Kolab::ObjectType objectType); QVariant parseMimeMessage(const KMime::Message::Ptr &msg); QVariant parseMimeMessage(const std::string &s); ObjectType mObjectType; Version mVersion; ObjectType mOverrideObjectType; Version mOverrideVersion; bool mDoOverrideVersion; QVariant mObject; }; //@endcond static std::vector getAttachments(const std::vector &attachments, const KMime::Message::Ptr &msg) { std::vector allAttachments; foreach (const Kolab::Attachment &attachment, attachments) { if (!attachment.uri().empty()) { const Kolab::Attachment extracted = Mime::getAttachment(attachment.uri(), msg); if (extracted.isValid()) { allAttachments.push_back(extracted); } } else { allAttachments.push_back(attachment); } } return allAttachments; } static std::vector getAttachments(const QStringList &attachmentNames, const KMime::Message::Ptr &msg) { std::vector allAttachments; foreach (const QString &name, attachmentNames) { const Kolab::Attachment extracted = Mime::getAttachmentByName(name, msg); if (extracted.isValid()) { allAttachments.push_back(extracted); } } return allAttachments; } QVariant MIMEObject::Private::readKolabV2(const KMime::Message::Ptr &msg, Kolab::ObjectType objectType) { if (objectType == DictionaryConfigurationObject) { KMime::Content *xmlContent = Mime::findContentByType(msg, "application/xml"); if (!xmlContent) { Critical() << "no application/xml part found"; printMessageDebugInfo(msg); return InvalidObject; } const QByteArray &xmlData = xmlContent->decodedContent(); QString dictionaryLanguage; const QStringList entries = Kolab::readLegacyDictionaryConfiguration(xmlData, dictionaryLanguage); mObjectType = objectType; Kolab::Dictionary dictionary(Conversion::toStdString(dictionaryLanguage)); std::vector convertedEntries; foreach (const QString &value, entries) { convertedEntries.push_back(Conversion::toStdString(value)); } dictionary.setEntries(convertedEntries); return QVariant::fromValue(Kolab::Configuration(dictionary)); } KMime::Content *xmlContent = Mime::findContentByType(msg, getTypeString(objectType)); if (!xmlContent) { Critical() << "no part with type" << getTypeString(objectType) << " found"; printMessageDebugInfo(msg); return QVariant(); } const QByteArray &xmlData = xmlContent->decodedContent(); - Q_ASSERT(!xmlData.isEmpty()); + if (xmlData.isEmpty()) { + Critical() << "no content in message part with type" << getTypeString(objectType); + printMessageDebugInfo(msg); + return QVariant(); + } QVariant variant; switch (objectType) { case EventObject: { QStringList attachments; KCalCore::Event::Ptr kEvent = fromXML(xmlData, attachments); Kolab::Event event = Kolab::Conversion::fromKCalCore(*kEvent); event.setAttachments(getAttachments(attachments, msg)); variant = QVariant::fromValue(event); break; } case TodoObject: { QStringList attachments; KCalCore::Todo::Ptr kTodo = fromXML(xmlData, attachments); Kolab::Todo todo = Kolab::Conversion::fromKCalCore(*kTodo); todo.setAttachments(getAttachments(attachments, msg)); variant = QVariant::fromValue(todo); break; } case JournalObject: { QStringList attachments; KCalCore::Journal::Ptr kJournal = fromXML(xmlData, attachments); Kolab::Journal journal = Kolab::Conversion::fromKCalCore(*kJournal); journal.setAttachments(getAttachments(attachments, msg)); variant = QVariant::fromValue(journal); break; } case ContactObject: { KContacts::Addressee kContact= addresseeFromKolab(xmlData, msg); Kolab::Contact contact = Kolab::Conversion::fromKABC(kContact); variant = QVariant::fromValue(contact); break; } case DistlistObject: { KContacts::ContactGroup kContactGroup= contactGroupFromKolab(xmlData); Kolab::DistList distlist = Kolab::Conversion::fromKABC(kContactGroup); variant = QVariant::fromValue(distlist); break; } case NoteObject: { KMime::Message::Ptr kNote = noteFromKolab(xmlData, KDateTime(msg->date()->dateTime())); Kolab::Note note = Kolab::Conversion::fromNote(kNote); variant = QVariant::fromValue(note); break; } default: CRITICAL("no kolab object found "); break; } if (ErrorHandler::errorOccured()) { printMessageDebugInfo(msg); return QVariant(); } mObjectType = objectType; return variant; } QVariant MIMEObject::Private::readKolabV3(const KMime::Message::Ptr &msg, Kolab::ObjectType objectType) { KMime::Content * const xmlContent = Mime::findContentByType(msg, getMimeType(objectType)); if (!xmlContent) { Critical() << "no " << getMimeType(objectType) << " part found"; printMessageDebugInfo(msg); return InvalidObject; } const QByteArray &content = xmlContent->decodedContent(); const std::string xml = std::string(content.data(), content.size()); QVariant variant; switch (objectType) { case EventObject: { Kolab::Event event = Kolab::readEvent(xml, false); event.setAttachments(getAttachments(event.attachments(), msg)); variant = QVariant::fromValue(event); break; } case TodoObject: { Kolab::Todo todo = Kolab::readTodo(xml, false); todo.setAttachments(getAttachments(todo.attachments(), msg)); variant = QVariant::fromValue(todo); break; } case JournalObject: { Kolab::Journal journal = Kolab::readJournal(xml, false); journal.setAttachments(getAttachments(journal.attachments(), msg)); variant = QVariant::fromValue(journal); break; } case ContactObject: variant = QVariant::fromValue(Kolab::readContact(xml, false)); break; case DistlistObject: variant = QVariant::fromValue(Kolab::readDistlist(xml, false)); break; case NoteObject: variant = QVariant::fromValue(Kolab::readNote(xml, false)); break; case FreebusyObject: variant = QVariant::fromValue(Kolab::readFreebusy(xml, false)); break; case DictionaryConfigurationObject: case RelationConfigurationObject: variant = QVariant::fromValue(Kolab::readConfiguration(xml, false)); break; default: Critical() << "no kolab object found "; printMessageDebugInfo(msg); break; } if (ErrorHandler::errorOccured()) { printMessageDebugInfo(msg); return QVariant(); } mObjectType = objectType; return variant; } QVariant MIMEObject::Private::parseMimeMessage(const KMime::Message::Ptr &msg) { ErrorHandler::clearErrors(); mObjectType = InvalidObject; if (msg->contents().isEmpty()) { Critical() << "message has no contents (we likely failed to parse it correctly)"; printMessageDebugInfo(msg); return QVariant(); } Kolab::ObjectType objectType = InvalidObject; if (mOverrideObjectType == InvalidObject) { if (KMime::Headers::Base *xKolabHeader = msg->headerByType(X_KOLAB_TYPE_HEADER)) { objectType = getObjectType(xKolabHeader->asUnicodeString().trimmed().toStdString()); } else { Warning() << "could not find the X-Kolab-Type Header, trying autodetection" ; //This works only for v2 messages atm. objectType = detectType(msg); } } else { objectType = mOverrideObjectType; } if (objectType == InvalidObject) { Critical() << "unable to detect object type"; printMessageDebugInfo(msg); return QVariant(); } if (!mDoOverrideVersion) { KMime::Headers::Base *xKolabVersion = msg->headerByType(X_KOLAB_MIME_VERSION_HEADER); if (!xKolabVersion) { //For backwards compatibility to development versions, can be removed in future versions xKolabVersion = msg->headerByType(X_KOLAB_MIME_VERSION_HEADER_COMPAT); } if (!xKolabVersion || xKolabVersion->asUnicodeString() == KOLAB_VERSION_V2) { mVersion = KolabV2; } else { if (xKolabVersion->asUnicodeString() != KOLAB_VERSION_V3) { //TODO version compatibility check? Warning() << "Kolab Version Header available but not on the same version as the implementation: " << xKolabVersion->asUnicodeString(); } mVersion = KolabV3; } } else { mVersion = mOverrideVersion; } if (mVersion == KolabV2) { return readKolabV2(msg, objectType); } return readKolabV3(msg, objectType); } QVariant MIMEObject::Private::parseMimeMessage(const std::string &s) { KMime::Message::Ptr msg(new KMime::Message); msg->setContent(QByteArray(s.c_str())); msg->parse(); return parseMimeMessage(msg); } MIMEObject::MIMEObject() : d(new MIMEObject::Private) { } void MIMEObject::setObjectType(ObjectType type) { d->mOverrideObjectType = type; } void MIMEObject::setVersion(Version version) { d->mOverrideVersion = version; d->mDoOverrideVersion = true; } static std::string createCid() { return QString::fromLatin1("cid:%1@%2").arg(KRandom::randomString(16)).arg("kolab.resource.akonadi").toStdString(); } std::vector convertToReferences(const std::vector &attachments, std::vector &attachmentCids) { std::vector attachmentsWithReferences; Q_FOREACH (const Kolab::Attachment &a, attachments) { Kolab::Attachment attachment; attachment.setLabel(a.label()); const std::string cid = a.uri().empty() ? createCid() : a.uri(); attachmentCids.push_back(cid); attachment.setUri(cid, a.mimetype()); //Serialize the attachment as attachment with uri, referencing the created mime-part attachmentsWithReferences.push_back(attachment); } return attachmentsWithReferences; } template static T convertAttachmentsToReferences(const T &incidence, std::vector &attachmentCids) { T removedAttachments = incidence; removedAttachments.setAttachments(convertToReferences(incidence.attachments(), attachmentCids)); return removedAttachments; } static void addAttachments(KMime::Message::Ptr msg, const std::vector &attachments, std::vector &attachmentCids) { int index = 0; foreach (const Attachment &attachment, attachments) { const std::string data = attachment.data(); const std::string cid = attachmentCids.empty() ? attachment.uri() : attachmentCids.at(index); msg->addContent(Mime::createAttachmentPart(Mime::fromCid(QByteArray(cid.c_str())).toLatin1(), QByteArray(attachment.mimetype().c_str()), QString::fromStdString(attachment.label()), QByteArray::fromRawData(data.c_str(), data.size()))); index++; } } ObjectType MIMEObject::parseMessage(const std::string &msg) { d->mObject = d->parseMimeMessage(msg); return d->mObjectType; } ObjectType MIMEObject::getType() const { return d->mObjectType; } Version MIMEObject::getVersion() const { return d->mVersion; } Kolab::Event MIMEObject::getEvent() const { return d->mObject.value(); } Kolab::Todo MIMEObject::getTodo() const { return d->mObject.value(); } Kolab::Journal MIMEObject::getJournal() const { return d->mObject.value(); } Kolab::Note MIMEObject::getNote() const { return d->mObject.value(); } Kolab::Contact MIMEObject::getContact() const { return d->mObject.value(); } Kolab::DistList MIMEObject::getDistlist() const { return d->mObject.value(); } Kolab::Freebusy MIMEObject::getFreebusy() const { return d->mObject.value(); } Kolab::Configuration MIMEObject::getConfiguration() const { return d->mObject.value(); } std::string MIMEObject::writeEvent(const Event &event, Version version, const std::string &pId) { ErrorHandler::clearErrors(); const std::string productId = getProductId(pId); KMime::Message::Ptr msg; Kolab::XMLObject xmlObject; std::vector attachmentCids; if (version == KolabV3) { const std::string xml = xmlObject.writeEvent(convertAttachmentsToReferences(event, attachmentCids), version, productId); msg = Mime::createMessage(xCalMimeType(), eventKolabType(), xml, true, productId, event.organizer().email(), event.organizer().name(), event.uid()); } else if (version == KolabV2) { const std::string xml = xmlObject.writeEvent(event, version, productId); msg = Mime::createMessage(eventKolabType(), eventKolabType(), xml, false, productId, event.organizer().email(), event.organizer().name(), event.uid()); } addAttachments(msg, event.attachments(), attachmentCids); msg->assemble(); return msg->encodedContent().data(); } Event MIMEObject::readEvent(const std::string &s) { return d->parseMimeMessage(s).value(); } std::string MIMEObject::writeTodo(const Todo &todo, Version version, const std::string &pId) { ErrorHandler::clearErrors(); const std::string productId = getProductId(pId); KMime::Message::Ptr msg; Kolab::XMLObject xmlObject; std::vector attachmentCids; if (version == KolabV3) { const std::string xml = xmlObject.writeTodo(convertAttachmentsToReferences(todo, attachmentCids), version, productId); msg = Mime::createMessage(xCalMimeType(), todoKolabType(), xml, true, productId, todo.organizer().email(), todo.organizer().name(), todo.uid()); } else if (version == KolabV2) { const std::string xml = xmlObject.writeTodo(todo, version, productId); msg = Mime::createMessage(todoKolabType(), todoKolabType(), xml, false, productId, todo.organizer().email(), todo.organizer().name(), todo.uid()); } addAttachments(msg, todo.attachments(), attachmentCids); msg->assemble(); return msg->encodedContent().data(); } Todo MIMEObject::readTodo(const std::string &s) { return d->parseMimeMessage(s).value(); } std::string MIMEObject::writeJournal(const Journal &journal, Version version, const std::string &pId) { ErrorHandler::clearErrors(); const std::string productId = getProductId(pId); KMime::Message::Ptr msg; Kolab::XMLObject xmlObject; std::vector attachmentCids; if (version == KolabV3) { const std::string xml = xmlObject.writeJournal(convertAttachmentsToReferences(journal, attachmentCids), version, productId); msg = Mime::createMessage(xCalMimeType(), journalKolabType(), xml, true, productId, std::string(), std::string(), journal.uid()); } else if (version == KolabV2) { const std::string xml = xmlObject.writeJournal(journal, version, productId); msg = Mime::createMessage(journalKolabType(), journalKolabType(), xml, false, productId, std::string(), std::string(), journal.uid()); } addAttachments(msg, journal.attachments(), attachmentCids); msg->assemble(); return msg->encodedContent().data(); } Journal MIMEObject::readJournal(const std::string &s){ return d->parseMimeMessage(s).value(); } std::string MIMEObject::writeNote(const Note ¬e, Version version, const std::string &pId) { ErrorHandler::clearErrors(); const std::string productId = getProductId(pId); KMime::Message::Ptr msg; Kolab::XMLObject xmlObject; std::vector attachmentCids; if (version == KolabV3) { const std::string xml = xmlObject.writeNote(convertAttachmentsToReferences(note, attachmentCids), version, productId); msg = Mime::createMessage(kolabMimeType(), noteKolabType(), xml, true, productId, std::string(), std::string(), note.uid()); } else if (version == KolabV2) { const std::string xml = xmlObject.writeNote(note, version, productId); msg = Mime::createMessage(noteKolabType(), noteKolabType(), xml, false, productId, std::string(), std::string(), note.uid()); } addAttachments(msg, note.attachments(), attachmentCids); msg->assemble(); return msg->encodedContent().data(); } Note MIMEObject::readNote(const std::string &s){ return d->parseMimeMessage(s).value(); } std::string MIMEObject::writeContact(const Contact &contact, Version version, const std::string &pId) { ErrorHandler::clearErrors(); const std::string productId = getProductId(pId); KMime::Message::Ptr msg; Kolab::XMLObject xmlObject; const std::string xml = xmlObject.writeContact(contact, version, productId); Email preferredEmail = !contact.emailAddresses().empty() ? contact.emailAddresses().at(contact.emailAddressPreferredIndex()) : Email(); QPair pair = Conversion::fromMailto(preferredEmail.address()); std::string name = pair.second; std::string email = pair.first; if (name.empty()) { name = contact.name(); } if (version == KolabV3) { msg = Mime::createMessage(xCardMimeType(), contactKolabType(), xml, true, productId, email, name, contact.uid()); } else if (version == KolabV2) { msg = Mime::createMessage(contactKolabType(), contactKolabType(), xml, false, productId, email, name, contact.uid()); } msg->assemble(); return msg->encodedContent().data(); } Contact MIMEObject::readContact(const std::string &s){ return d->parseMimeMessage(s).value(); } std::string MIMEObject::writeDistlist(const DistList &distlist, Version version, const std::string &pId) { ErrorHandler::clearErrors(); const std::string productId = getProductId(pId); KMime::Message::Ptr msg; Kolab::XMLObject xmlObject; const std::string xml = xmlObject.writeDistlist(distlist, version, productId); if (version == KolabV3) { msg = Mime::createMessage(xCardMimeType(), distlistKolabType(), xml, true, productId, std::string(), std::string(), distlist.uid()); } else if (version == KolabV2) { msg = Mime::createMessage(distlistKolabType(), distlistKolabType(), xml, false, productId, std::string(), std::string(), distlist.uid()); } msg->assemble(); return msg->encodedContent().data(); } DistList MIMEObject::readDistlist(const std::string &s) { return d->parseMimeMessage(s).value(); } std::string MIMEObject::writeConfiguration(const Configuration &configuration, Version version, const std::string& pId) { ErrorHandler::clearErrors(); const std::string productId = getProductId(pId); KMime::Message::Ptr msg; Kolab::XMLObject xmlObject; const std::string xml = xmlObject.writeConfiguration(configuration, version, productId); std::string kolabType; switch (configuration.type()) { case Kolab::Configuration::TypeDictionary: kolabType = dictKolabType(); break; case Kolab::Configuration::TypeRelation: kolabType = relationKolabType(); break; case Kolab::Configuration::TypeSnippet: kolabType = configurationKolabType(); break; case Kolab::Configuration::TypeFileDriver: kolabType = configurationKolabType(); break; case Kolab::Configuration::TypeCategoryColor: kolabType = configurationKolabType(); break; default: break; } if (version == KolabV3) { msg = Mime::createMessage(kolabMimeType(), kolabType, xml, true, productId, std::string(), std::string(), configuration.uid()); } else if (version == KolabV2) { Critical() << "Not available in KolabV2"; } msg->assemble(); return msg->encodedContent().data(); } Configuration MIMEObject::readConfiguration(const std::string &s) { return d->parseMimeMessage(s).value(); } std::string MIMEObject::writeFreebusy(const Freebusy &freebusy, Version version, const std::string& pId) { ErrorHandler::clearErrors(); const std::string productId = getProductId(pId); KMime::Message::Ptr msg; Kolab::XMLObject xmlObject; const std::string xml = xmlObject.writeFreebusy(freebusy, version, productId); if (version == KolabV3) { msg = Mime::createMessage(xCalMimeType(), freebusyKolabType(), xml, true, productId, std::string(), std::string(), freebusy.uid()); } else if (version == KolabV2) { Critical() << "Not available in KolabV2"; } msg->assemble(); return msg->encodedContent().data(); } Freebusy MIMEObject::readFreebusy(const std::string &s) { return d->parseMimeMessage(s).value(); } }