File indexing completed on 2024-05-12 05:13:14

0001 /*
0002   SPDX-FileCopyrightText: 2010 Klarlvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
0003   SPDX-FileContributor: Allen Winter <allen.winter@kdab.com>
0004 
0005   SPDX-FileCopyrightText: 2014 Sergio Martins <iamsergio@gmail.com>
0006 
0007   SPDX-License-Identifier: LGPL-2.0-or-later
0008 */
0009 
0010 /**
0011   @file
0012   This file is part of the API for handling calendar data and provides
0013   static functions for dealing with calendar incidence attachments.
0014 
0015   @brief
0016   vCalendar/iCalendar attachment handling.
0017 
0018   @author Allen Winter \<winter@kde.org\>
0019 */
0020 #include "attachmenthandler.h"
0021 #include "calendarsupport_debug.h"
0022 
0023 #include <Akonadi/CalendarUtils>
0024 #include <Akonadi/ItemFetchJob>
0025 
0026 #include <KIO/FileCopyJob>
0027 #include <KIO/JobUiDelegate>
0028 #include <KIO/OpenUrlJob>
0029 #include <KIO/StatJob>
0030 #include <KJob>
0031 #include <KJobWidgets>
0032 #include <KLocalizedString>
0033 #include <KMessageBox>
0034 
0035 #include <QDesktopServices>
0036 #include <QFile>
0037 #include <QFileDialog>
0038 #include <QMimeDatabase>
0039 #include <QPointer>
0040 #include <QTemporaryFile>
0041 
0042 using namespace KCalendarCore;
0043 using namespace Akonadi;
0044 
0045 namespace CalendarSupport
0046 {
0047 struct ReceivedInfo {
0048     QString uid;
0049     QString attachmentName;
0050 };
0051 
0052 class AttachmentHandlerPrivate
0053 {
0054 public:
0055     explicit AttachmentHandlerPrivate(QWidget *parent)
0056         : mParent(parent)
0057     {
0058     }
0059 
0060     QMap<KJob *, ReceivedInfo> mJobToReceivedInfo;
0061     QPointer<QWidget> const mParent;
0062 };
0063 
0064 AttachmentHandler::AttachmentHandler(QWidget *parent)
0065     : QObject(parent)
0066     , d(new AttachmentHandlerPrivate(parent))
0067 {
0068 }
0069 
0070 AttachmentHandler::~AttachmentHandler() = default;
0071 
0072 Attachment AttachmentHandler::find(const QString &attachmentName, const Incidence::Ptr &incidence)
0073 {
0074     if (!incidence) {
0075         return Attachment();
0076     }
0077 
0078     // get the attachment by name from the incidence
0079     const Attachment::List as = incidence->attachments();
0080     Attachment a;
0081     if (!as.isEmpty()) {
0082         Attachment::List::ConstIterator it;
0083         Attachment::List::ConstIterator end(as.constEnd());
0084 
0085         for (it = as.constBegin(); it != end; ++it) {
0086             if ((*it).label() == attachmentName) {
0087                 a = *it;
0088                 break;
0089             }
0090         }
0091     }
0092 
0093     if (a.isEmpty()) {
0094         KMessageBox::error(d->mParent, i18n("No attachment named \"%1\" found in the incidence.", attachmentName));
0095         return Attachment();
0096     }
0097 
0098     if (a.isUri()) {
0099         auto job = KIO::stat(QUrl(a.uri()), KIO::StatJob::SourceSide, KIO::StatBasic);
0100 
0101         KJobWidgets::setWindow(job, d->mParent);
0102         if (!job->exec()) {
0103             KMessageBox::error(
0104                 d->mParent,
0105                 i18n("The attachment \"%1\" is a web link that is inaccessible from this computer. ", QUrl::fromPercentEncoding(a.uri().toLatin1())));
0106             return Attachment();
0107         }
0108     }
0109     return a;
0110 }
0111 
0112 Attachment AttachmentHandler::find(const QString &attachmentName, const ScheduleMessage::Ptr &message)
0113 {
0114     if (!message) {
0115         return Attachment();
0116     }
0117 
0118     Incidence::Ptr incidence = message->event().dynamicCast<Incidence>();
0119     if (!incidence) {
0120         KMessageBox::error(d->mParent,
0121                            i18n("The calendar invitation stored in this email message is broken in some way. "
0122                                 "Unable to continue."));
0123         return Attachment();
0124     }
0125 
0126     return find(attachmentName, incidence);
0127 }
0128 
0129 static QTemporaryFile *s_tempFile = nullptr;
0130 
0131 static QUrl tempFileForAttachment(const Attachment &attachment)
0132 {
0133     QUrl url;
0134 
0135     QMimeDatabase db;
0136     QStringList patterns = db.mimeTypeForName(attachment.mimeType()).globPatterns();
0137     if (!patterns.empty()) {
0138         s_tempFile = new QTemporaryFile(QDir::tempPath() + QLatin1StringView("/attachementview_XXXXXX") + patterns.first().remove(QLatin1Char('*')));
0139     } else {
0140         s_tempFile = new QTemporaryFile();
0141     }
0142     s_tempFile->setAutoRemove(false);
0143     s_tempFile->open();
0144     s_tempFile->setPermissions(QFile::ReadUser);
0145     s_tempFile->write(QByteArray::fromBase64(attachment.data()));
0146     s_tempFile->close();
0147     QFile tf(s_tempFile->fileName());
0148     if (tf.size() != attachment.size()) {
0149         // whoops. failed to write the entire attachment. return an invalid URL.
0150         delete s_tempFile;
0151         s_tempFile = nullptr;
0152         return url;
0153     }
0154 
0155     url.setPath(s_tempFile->fileName());
0156     return url;
0157 }
0158 
0159 bool AttachmentHandler::view(const Attachment &attachment)
0160 {
0161     if (attachment.isEmpty()) {
0162         return false;
0163     }
0164 
0165     bool stat = true;
0166     if (attachment.isUri()) {
0167         QDesktopServices::openUrl(QUrl(attachment.uri()));
0168     } else {
0169         // put the attachment in a temporary file and launch it
0170         QUrl tempUrl = tempFileForAttachment(attachment);
0171         if (tempUrl.isValid()) {
0172             auto job = new KIO::OpenUrlJob(tempUrl, attachment.mimeType());
0173             job->setDeleteTemporaryFile(true);
0174             job->setRunExecutables(true);
0175             job->start();
0176         } else {
0177             stat = false;
0178             KMessageBox::error(d->mParent, i18n("Unable to create a temporary file for the attachment."));
0179         }
0180         delete s_tempFile;
0181         s_tempFile = nullptr;
0182     }
0183     return stat;
0184 }
0185 
0186 bool AttachmentHandler::view(const QString &attachmentName, const Incidence::Ptr &incidence)
0187 {
0188     return view(find(attachmentName, incidence));
0189 }
0190 
0191 void AttachmentHandler::view(const QString &attachmentName, const QString &uid)
0192 {
0193     Item item;
0194     item.setGid(uid);
0195     auto job = new ItemFetchJob(item);
0196     connect(job, &ItemFetchJob::result, this, &AttachmentHandler::slotFinishView);
0197     ReceivedInfo info;
0198     info.attachmentName = attachmentName;
0199     info.uid = uid;
0200     d->mJobToReceivedInfo[job] = info;
0201 }
0202 
0203 bool AttachmentHandler::view(const QString &attachmentName, const ScheduleMessage::Ptr &message)
0204 {
0205     return view(find(attachmentName, message));
0206 }
0207 
0208 bool AttachmentHandler::saveAs(const Attachment &attachment)
0209 {
0210     // get the saveas file name
0211     const QString saveAsFile = QFileDialog::getSaveFileName(d->mParent, i18n("Save Attachment"), attachment.label());
0212     if (saveAsFile.isEmpty()) {
0213         return false;
0214     }
0215 
0216     bool stat = false;
0217     if (attachment.isUri()) {
0218         // save the attachment url
0219         auto job = KIO::file_copy(QUrl(attachment.uri()), QUrl::fromLocalFile(saveAsFile));
0220         stat = job->exec();
0221     } else {
0222         // put the attachment in a temporary file and save it
0223         QUrl tempUrl = tempFileForAttachment(attachment);
0224         if (tempUrl.isValid()) {
0225             auto job = KIO::file_copy(tempUrl, QUrl::fromLocalFile(saveAsFile));
0226             stat = job->exec();
0227             if (!stat && job->error()) {
0228                 KMessageBox::error(d->mParent, job->errorString());
0229             }
0230         } else {
0231             stat = false;
0232             KMessageBox::error(d->mParent, i18n("Unable to create a temporary file for the attachment."));
0233         }
0234         delete s_tempFile;
0235         s_tempFile = nullptr;
0236     }
0237     return stat;
0238 }
0239 
0240 bool AttachmentHandler::saveAs(const QString &attachmentName, const Incidence::Ptr &incidence)
0241 {
0242     return saveAs(find(attachmentName, incidence));
0243 }
0244 
0245 void AttachmentHandler::saveAs(const QString &attachmentName, const QString &uid)
0246 {
0247     Item item;
0248     item.setGid(uid);
0249     auto job = new ItemFetchJob(item);
0250     connect(job, &ItemFetchJob::result, this, &AttachmentHandler::slotFinishView);
0251 
0252     ReceivedInfo info;
0253     info.attachmentName = attachmentName;
0254     info.uid = uid;
0255     d->mJobToReceivedInfo[job] = info;
0256 }
0257 
0258 bool AttachmentHandler::saveAs(const QString &attachmentName, const ScheduleMessage::Ptr &message)
0259 {
0260     return saveAs(find(attachmentName, message));
0261 }
0262 
0263 void AttachmentHandler::slotFinishSaveAs(KJob *job)
0264 {
0265     ReceivedInfo info = d->mJobToReceivedInfo[job];
0266     bool success = false;
0267 
0268     if (job->error() != 0) {
0269         auto fetchJob = qobject_cast<ItemFetchJob *>(job);
0270         const Item::List items = fetchJob->items();
0271         if (!items.isEmpty()) {
0272             Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(items.first());
0273             success = incidence && saveAs(info.attachmentName, incidence);
0274         } else {
0275             qCWarning(CALENDARSUPPORT_LOG) << Q_FUNC_INFO << "No item found";
0276         }
0277     } else {
0278         qCWarning(CALENDARSUPPORT_LOG) << Q_FUNC_INFO << "Job error:" << job->errorString();
0279     }
0280 
0281     Q_EMIT saveAsFinished(info.uid, info.attachmentName, success);
0282     d->mJobToReceivedInfo.remove(job);
0283 }
0284 
0285 void AttachmentHandler::slotFinishView(KJob *job)
0286 {
0287     ReceivedInfo info = d->mJobToReceivedInfo[job];
0288     bool success = false;
0289 
0290     if (job->error()) {
0291         auto fetchJob = qobject_cast<ItemFetchJob *>(job);
0292         const Item::List items = fetchJob->items();
0293         if (!items.isEmpty()) {
0294             Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(items.first());
0295             success = incidence && view(info.attachmentName, incidence);
0296         } else {
0297             qCWarning(CALENDARSUPPORT_LOG) << Q_FUNC_INFO << "No item found";
0298         }
0299     } else {
0300         qCWarning(CALENDARSUPPORT_LOG) << Q_FUNC_INFO << "Job error:" << job->errorString();
0301     }
0302 
0303     Q_EMIT viewFinished(info.uid, info.attachmentName, success);
0304     d->mJobToReceivedInfo.remove(job);
0305 }
0306 } // namespace CalendarSupport
0307 
0308 #include "moc_attachmenthandler.cpp"