Warning, file /pim/kitinerary/src/lib/file.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "file.h"
0008 #include "jsonlddocument.h"
0009 #include "logging.h"
0010 
0011 #include <KItinerary/CreativeWork>
0012 
0013 #include <KPkPass/Pass>
0014 
0015 #include <KZip>
0016 
0017 #include <QDebug>
0018 #include <QJsonArray>
0019 #include <QJsonDocument>
0020 #include <QJsonObject>
0021 #include <QString>
0022 #include <QUuid>
0023 
0024 using namespace KItinerary;
0025 
0026 namespace KItinerary {
0027 class FilePrivate
0028 {
0029 public:
0030     QString fileName;
0031     QIODevice *device = nullptr;
0032     std::unique_ptr<KZip> zipFile;
0033 };
0034 }
0035 
0036 File::File()
0037     : d(new FilePrivate)
0038 {
0039 }
0040 
0041 File::File(const QString &fileName)
0042     : d(new FilePrivate)
0043 {
0044     d->fileName = fileName;
0045 }
0046 
0047 File::File(QIODevice* device)
0048     : d(new FilePrivate)
0049 {
0050     d->device = device;
0051 }
0052 
0053 File::File(KItinerary::File &&) = default;
0054 
0055 File::~File()
0056 {
0057     close();
0058 }
0059 
0060 File& KItinerary::File::operator=(KItinerary::File &&) = default;
0061 
0062 void File::setFileName(const QString &fileName)
0063 {
0064     d->fileName = fileName;
0065 }
0066 
0067 bool File::open(File::OpenMode mode) const
0068 {
0069     if (d->device) {
0070         d->zipFile = std::make_unique<KZip>(d->device);
0071     } else {
0072         d->zipFile = std::make_unique<KZip>(d->fileName);
0073     }
0074 
0075     if (!d->zipFile->open(mode == File::Write ? QIODevice::WriteOnly : QIODevice::ReadOnly)) {
0076         qCWarning(Log) << d->zipFile->errorString() << d->fileName;
0077         return false;
0078     }
0079 
0080     return true;
0081 }
0082 
0083 QString File::errorString() const
0084 {
0085     if (d->zipFile && !d->zipFile->isOpen()) {
0086         return d->zipFile->errorString();
0087     }
0088     return {};
0089 }
0090 
0091 void File::close()
0092 {
0093     if (d->zipFile) {
0094         d->zipFile->close();
0095     }
0096     d->zipFile.reset();
0097 }
0098 
0099 QList<QString> File::reservations() const {
0100     Q_ASSERT(d->zipFile);
0101     const auto resDir = dynamic_cast<const KArchiveDirectory *>(
0102         d->zipFile->directory()->entry(QLatin1StringView("reservations")));
0103     if (!resDir) {
0104         return {};
0105     }
0106 
0107     const auto entries = resDir->entries();
0108     QList<QString> res;
0109     res.reserve(entries.size());
0110     for (const auto &entry : entries) {
0111       if (!entry.endsWith(QLatin1StringView(".json"))) {
0112         continue;
0113       }
0114         res.push_back(entry.left(entry.size() - 5));
0115     }
0116 
0117     return res;
0118 }
0119 
0120 QVariant File::reservation(const QString &resId) const
0121 {
0122     Q_ASSERT(d->zipFile);
0123     const auto resDir = dynamic_cast<const KArchiveDirectory *>(
0124         d->zipFile->directory()->entry(QLatin1StringView("reservations")));
0125     if (!resDir) {
0126         return {};
0127     }
0128 
0129     const auto file = resDir->file(resId + QLatin1StringView(".json"));
0130     if (!file) {
0131         qCDebug(Log) << "reservation not found" << resId;
0132         return {};
0133     }
0134 
0135     const auto doc = QJsonDocument::fromJson(file->data());
0136     if (doc.isArray()) {
0137         const auto array = JsonLdDocument::fromJson(doc.array());
0138         if (array.size() != 1) {
0139             qCWarning(Log) << "reservation file for" << resId << "contains" << array.size() << "elements!";
0140             return {};
0141         }
0142         return array.at(0);
0143     } else if (doc.isObject()) {
0144         return JsonLdDocument::fromJsonSingular(doc.object());
0145     }
0146     return {};
0147 }
0148 
0149 void File::addReservation(const QVariant &res)
0150 {
0151     addReservation(QUuid::createUuid().toString(QUuid::WithoutBraces), res);
0152 }
0153 
0154 void File::addReservation(const QString &id, const QVariant &res)
0155 {
0156     Q_ASSERT(d->zipFile);
0157     d->zipFile->writeFile(QLatin1StringView("reservations/") + id +
0158                               QLatin1String(".json"),
0159                           QJsonDocument(JsonLdDocument::toJson(res)).toJson());
0160 }
0161 
0162 QString File::passId(const KPkPass::Pass *pass)
0163 {
0164     return passId(pass->passTypeIdentifier(), pass->serialNumber());
0165 }
0166 
0167 QString File::passId(const QString &passTypeIdenfier, const QString &serialNumber)
0168 {
0169     if (passTypeIdenfier.isEmpty() || serialNumber.isEmpty()) {
0170         return {};
0171     }
0172     // serialNumber can contain percent-encoding or slashes, ie stuff we don't want to have in file names
0173     return passTypeIdenfier + QLatin1Char('/') + QString::fromUtf8(serialNumber.toUtf8().toBase64(QByteArray::Base64UrlEncoding));
0174 }
0175 
0176 QList<QString> File::passes() const {
0177     Q_ASSERT(d->zipFile);
0178     const auto passDir = dynamic_cast<const KArchiveDirectory *>(
0179         d->zipFile->directory()->entry(QLatin1StringView("passes")));
0180     if (!passDir) {
0181         return {};
0182     }
0183 
0184     const auto entries = passDir->entries();
0185     QList<QString> passIds;
0186     for (const auto &entry : entries) {
0187         const auto subdir = dynamic_cast<const KArchiveDirectory*>(passDir->entry(entry));
0188         if (!subdir) {
0189             continue;
0190         }
0191 
0192         const auto subEntries = subdir->entries();
0193         for (const auto &subEntry : subEntries) {
0194           if (!subEntry.endsWith(QLatin1StringView(".pkpass"))) {
0195             continue;
0196           }
0197             passIds.push_back(entry + QLatin1Char('/') + QStringView(subEntry).left(subEntry.size() - 7));
0198         }
0199     }
0200     return passIds;
0201 }
0202 
0203 QByteArray File::passData(const QString& passId) const
0204 {
0205     Q_ASSERT(d->zipFile);
0206     const auto passDir = dynamic_cast<const KArchiveDirectory *>(
0207         d->zipFile->directory()->entry(QLatin1StringView("passes")));
0208     if (!passDir) {
0209         return {};
0210     }
0211 
0212     const auto file = passDir->file(passId + QLatin1StringView(".pkpass"));
0213     if (!file) {
0214         qCDebug(Log) << "pass not found" << passId;
0215         return {};
0216     }
0217     return file->data();
0218 }
0219 
0220 void File::addPass(KPkPass::Pass* pass, const QByteArray& rawData)
0221 {
0222     addPass(passId(pass), rawData);
0223 }
0224 
0225 void File::addPass(const QString &passId, const QByteArray& rawData)
0226 {
0227     Q_ASSERT(d->zipFile);
0228     d->zipFile->writeFile(QLatin1StringView("passes/") + passId +
0229                               QLatin1String(".pkpass"),
0230                           rawData);
0231 }
0232 
0233 QList<QString> File::documents() const {
0234   const auto docDir = dynamic_cast<const KArchiveDirectory *>(
0235       d->zipFile->directory()->entry(QLatin1StringView("documents")));
0236   if (!docDir) {
0237     return {};
0238     }
0239 
0240     const auto entries = docDir->entries();
0241     QList<QString> res;
0242     res.reserve(entries.size());
0243     for (const auto &entry : entries) {
0244         if (docDir->entry(entry)->isDirectory()) {
0245             res.push_back(entry);
0246         }
0247     }
0248 
0249     return res;
0250 }
0251 
0252 QVariant File::documentInfo(const QString &id) const
0253 {
0254     Q_ASSERT(d->zipFile);
0255     const auto dir = dynamic_cast<const KArchiveDirectory *>(
0256         d->zipFile->directory()->entry(QLatin1StringView("documents/") + id));
0257     if (!dir) {
0258         return {};
0259     }
0260 
0261     const auto file = dir->file(QStringLiteral("meta.json"));
0262     if (!file) {
0263         qCDebug(Log) << "document meta data not found" << id;
0264         return {};
0265     }
0266 
0267     const auto doc = QJsonDocument::fromJson(file->data());
0268     if (doc.isArray()) {
0269         const auto array = JsonLdDocument::fromJson(doc.array());
0270         if (array.size() != 1) {
0271             qCWarning(Log) << "document meta data for" << id << "contains" << array.size() << "elements!";
0272             return {};
0273         }
0274         return array.at(0);
0275     } else if (doc.isObject()) {
0276         return JsonLdDocument::fromJsonSingular(doc.object());
0277     }
0278     return {};
0279 }
0280 
0281 QByteArray File::documentData(const QString &id) const
0282 {
0283     const auto meta = documentInfo(id);
0284     if (!JsonLd::canConvert<CreativeWork>(meta)) {
0285         return {};
0286     }
0287     const auto fileName = JsonLd::convert<CreativeWork>(meta).name();
0288 
0289     const auto dir = dynamic_cast<const KArchiveDirectory *>(
0290         d->zipFile->directory()->entry(QLatin1StringView("documents/") + id));
0291     Q_ASSERT(dir); // checked by documentInfo already
0292     const auto file = dir->file(fileName);
0293     if (!file) {
0294         qCWarning(Log) << "document data not found" << id << fileName;
0295         return {};
0296     }
0297     return file->data();
0298 }
0299 
0300 QString File::normalizeDocumentFileName(const QString &name)
0301 {
0302     auto fileName = name;
0303     // normalize the filename to something we can safely deal with
0304     auto idx = fileName.lastIndexOf(QLatin1Char('/'));
0305     if (idx >= 0) {
0306         fileName = fileName.mid(idx + 1);
0307     }
0308     fileName.replace(QLatin1Char('?'), QLatin1Char('_'));
0309     fileName.replace(QLatin1Char('*'), QLatin1Char('_'));
0310     fileName.replace(QLatin1Char(' '), QLatin1Char('_'));
0311     fileName.replace(QLatin1Char('\\'), QLatin1Char('_'));
0312     if (fileName.isEmpty() || fileName == QLatin1StringView("meta.json")) {
0313       fileName = QStringLiteral("file");
0314     }
0315     return fileName;
0316 }
0317 
0318 void File::addDocument(const QString &id, const QVariant &docInfo, const QByteArray &docData)
0319 {
0320     Q_ASSERT(d->zipFile);
0321     if (!JsonLd::canConvert<CreativeWork>(docInfo)) {
0322         qCWarning(Log) << "Invalid document meta data" << docInfo;
0323         return;
0324     }
0325     if (id.isEmpty()) {
0326         qCWarning(Log) << "Trying to add a document with an empty identifier!";
0327         return;
0328     }
0329 
0330     const auto fileName = normalizeDocumentFileName(JsonLdDocument::readProperty(docInfo, "name").toString());
0331     auto normalizedDocInfo = docInfo;
0332     JsonLdDocument::writeProperty(normalizedDocInfo, "name", fileName);
0333 
0334     d->zipFile->writeFile(
0335         QLatin1StringView("documents/") + id + QLatin1String("/meta.json"),
0336         QJsonDocument(JsonLdDocument::toJson(normalizedDocInfo)).toJson());
0337     d->zipFile->writeFile(QLatin1StringView("documents/") + id +
0338                               QLatin1Char('/') + fileName,
0339                           docData);
0340 }
0341 
0342 QList<QString> File::listCustomData(const QString &scope) const {
0343     Q_ASSERT(d->zipFile);
0344     const auto dir = dynamic_cast<const KArchiveDirectory *>(
0345         d->zipFile->directory()->entry(QLatin1StringView("custom/") + scope));
0346     if (!dir) {
0347         return {};
0348     }
0349 
0350     const auto entries = dir->entries();
0351     QList<QString> res;
0352     res.reserve(entries.size());
0353     std::copy(entries.begin(), entries.end(), std::back_inserter(res));
0354     return res;
0355 }
0356 
0357 QByteArray File::customData(const QString& scope, const QString &id) const
0358 {
0359     Q_ASSERT(d->zipFile);
0360     const auto dir = dynamic_cast<const KArchiveDirectory *>(
0361         d->zipFile->directory()->entry(QLatin1StringView("custom/") + scope));
0362     if (!dir) {
0363         return {};
0364     }
0365 
0366     const auto file = dir->file(id);
0367     if (!file) {
0368         qCDebug(Log) << "custom data not found" << scope << id;
0369         return {};
0370     }
0371     return file->data();
0372 }
0373 
0374 void File::addCustomData(const QString &scope, const QString &id, const QByteArray &data)
0375 {
0376     Q_ASSERT(d->zipFile);
0377     d->zipFile->writeFile(
0378         QLatin1StringView("custom/") + scope + QLatin1Char('/') + id, data);
0379 }