/* * Copyright (C) 2015 Christian Mollekopf * * 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. */ #include "mailpreprocessor.h" #include #include #include #include "pipeline.h" #include "definitions.h" #include "applicationdomaintype.h" using namespace Sink; QString MailPropertyExtractor::getFilePathFromMimeMessagePath(const QString &s) const { return s; } struct MimeMessageReader { MimeMessageReader(const QString &mimeMessagePath) : f(mimeMessagePath), mapped(0), mappedSize(0) { if (mimeMessagePath.isNull()) { SinkTrace() << "No mime message"; return; } SinkTrace() << "Updating indexed properties " << mimeMessagePath; if (!f.open(QIODevice::ReadOnly)) { SinkWarning() << "Failed to open the file: " << mimeMessagePath; return; } if (!f.size()) { SinkWarning() << "The file is empty."; return; } mappedSize = qMin((qint64)8000, f.size()); mapped = f.map(0, mappedSize); if (!mapped) { SinkWarning() << "Failed to map the file: " << f.errorString(); return; } } KMime::Message::Ptr mimeMessage() { if (!mapped) { return KMime::Message::Ptr(); } Q_ASSERT(mapped); Q_ASSERT(mappedSize); auto msg = KMime::Message::Ptr(new KMime::Message); msg->setHead(KMime::CRLFtoLF(QByteArray::fromRawData(reinterpret_cast(mapped), mappedSize))); msg->parse(); return msg; } QFile f; uchar *mapped; qint64 mappedSize; }; static Sink::ApplicationDomain::Mail::Contact getContact(const KMime::Headers::Generics::MailboxList *header) { const auto name = header->displayNames().isEmpty() ? QString() : header->displayNames().first(); const auto address = header->addresses().isEmpty() ? QString() : header->addresses().first(); return Sink::ApplicationDomain::Mail::Contact{name, address}; } static QList getContactList(const KMime::Headers::Generics::AddressList *header) { QList list; for (const auto &mb : header->mailboxes()) { list << Sink::ApplicationDomain::Mail::Contact{mb.name(), mb.address()}; } return list; } static void updatedIndexedProperties(Sink::ApplicationDomain::Mail &mail, KMime::Message::Ptr msg) { mail.setExtractedSubject(msg->subject(true)->asUnicodeString()); mail.setExtractedSender(getContact(msg->from(true))); mail.setExtractedTo(getContactList(msg->to(true))); mail.setExtractedCc(getContactList(msg->cc(true))); mail.setExtractedBcc(getContactList(msg->bcc(true))); mail.setExtractedDate(msg->date(true)->dateTime()); //Ensure the mssageId is unique. //If there already is one with the same id we'd have to assign a new message id, which probably doesn't make any sense. //The last is the parent auto references = msg->references(true)->identifiers(); //The first is the parent auto inReplyTo = msg->inReplyTo(true)->identifiers(); QByteArray parentMessageId; if (!references.isEmpty()) { parentMessageId = references.last(); //TODO we could use the rest of the references header to complete the ancestry in case we have missing parents. } else { if (!inReplyTo.isEmpty()) { //According to RFC5256 we should ignore all but the first parentMessageId = inReplyTo.first(); } } //The rest should never change, unless we didn't have the headers available initially. auto messageId = msg->messageID(true)->identifier(); if (messageId.isEmpty()) { //reuse an existing messageis (on modification) auto existing = mail.getMessageId(); if (existing.isEmpty()) { auto tmp = KMime::Message::Ptr::create(); auto header = tmp->messageID(true); header->generate("kube.kde.org"); messageId = header->as7BitString(); SinkWarning() << "Message id is empty, generating one: " << messageId; } else { messageId = existing; } } mail.setExtractedMessageId(messageId); if (!parentMessageId.isEmpty()) { mail.setExtractedParentMessageId(parentMessageId); } } void MailPropertyExtractor::newEntity(Sink::ApplicationDomain::Mail &mail) { MimeMessageReader mimeMessageReader(getFilePathFromMimeMessagePath(mail.getMimeMessagePath())); auto msg = mimeMessageReader.mimeMessage(); if (msg) { updatedIndexedProperties(mail, msg); } } void MailPropertyExtractor::modifiedEntity(const Sink::ApplicationDomain::Mail &oldMail, Sink::ApplicationDomain::Mail &newMail) { MimeMessageReader mimeMessageReader(getFilePathFromMimeMessagePath(newMail.getMimeMessagePath())); auto msg = mimeMessageReader.mimeMessage(); if (msg) { updatedIndexedProperties(newMail, msg); } }