File indexing completed on 2024-09-15 04:36:24

0001 /*
0002     SPDX-FileCopyrightText: 2020 Daniel Vrátil <dvratil@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "akonadiprivate_debug.h"
0008 #include "compressionstream_p.h"
0009 
0010 #include <QByteArray>
0011 
0012 #include <lzma.h>
0013 
0014 #include <array>
0015 
0016 using namespace Akonadi;
0017 
0018 namespace
0019 {
0020 class LZMAErrorCategory : public std::error_category
0021 {
0022 public:
0023     const char *name() const noexcept override
0024     {
0025         return "lzma";
0026     }
0027     std::string message(int ev) const noexcept override
0028     {
0029         switch (static_cast<lzma_ret>(ev)) {
0030         case LZMA_OK:
0031             return "Operation completed successfully";
0032         case LZMA_STREAM_END:
0033             return "End of stream was reached";
0034         case LZMA_NO_CHECK:
0035             return "Input stream has no integrity check";
0036         case LZMA_UNSUPPORTED_CHECK:
0037             return "Cannot calculate the integrity check";
0038         case LZMA_GET_CHECK:
0039             return "Integrity check type is now available";
0040         case LZMA_MEM_ERROR:
0041             return "Cannot allocate memory";
0042         case LZMA_MEMLIMIT_ERROR:
0043             return "Memory usage limit was reached";
0044         case LZMA_FORMAT_ERROR:
0045             return "File format not recognized";
0046         case LZMA_OPTIONS_ERROR:
0047             return "Invalid or unsupported options";
0048         case LZMA_DATA_ERROR:
0049             return "Data is corrupt";
0050         case LZMA_BUF_ERROR:
0051             return "No progress is possible";
0052         case LZMA_PROG_ERROR:
0053             return "Programming error";
0054         }
0055 
0056         Q_UNREACHABLE();
0057     }
0058 };
0059 
0060 const LZMAErrorCategory &lzmaErrorCategory()
0061 {
0062     static const LZMAErrorCategory lzmaErrorCategory{};
0063     return lzmaErrorCategory;
0064 }
0065 
0066 } // namespace
0067 
0068 namespace std
0069 {
0070 template<>
0071 struct is_error_code_enum<lzma_ret> : std::true_type {
0072 };
0073 
0074 std::error_condition make_error_condition(lzma_ret ret)
0075 {
0076     return std::error_condition(static_cast<int>(ret), lzmaErrorCategory());
0077 }
0078 
0079 } // namespace std
0080 
0081 std::error_code make_error_code(lzma_ret e)
0082 {
0083     return {static_cast<int>(e), lzmaErrorCategory()};
0084 }
0085 
0086 class Akonadi::Compressor
0087 {
0088 public:
0089     std::error_code initialize(QIODevice::OpenMode openMode)
0090     {
0091         if (openMode == QIODevice::ReadOnly) {
0092             return lzma_auto_decoder(&mStream, 100 * 1024 * 1024 /* 100 MiB */, 0);
0093         } else {
0094             return lzma_easy_encoder(&mStream, LZMA_PRESET_DEFAULT, LZMA_CHECK_CRC32);
0095         }
0096     }
0097 
0098     void setInputBuffer(const char *data, qint64 size)
0099     {
0100         mStream.next_in = reinterpret_cast<const uint8_t *>(data);
0101         mStream.avail_in = size;
0102     }
0103 
0104     void setOutputBuffer(char *data, qint64 maxSize)
0105     {
0106         mStream.next_out = reinterpret_cast<uint8_t *>(data);
0107         mStream.avail_out = maxSize;
0108     }
0109 
0110     size_t inputBufferAvailable() const
0111     {
0112         return mStream.avail_in;
0113     }
0114 
0115     size_t outputBufferAvailable() const
0116     {
0117         return mStream.avail_out;
0118     }
0119 
0120     std::error_code finalize()
0121     {
0122         lzma_end(&mStream);
0123         return LZMA_OK;
0124     }
0125 
0126     std::error_code inflate()
0127     {
0128         return lzma_code(&mStream, LZMA_RUN);
0129     }
0130 
0131     std::error_code deflate(bool finish)
0132     {
0133         return lzma_code(&mStream, finish ? LZMA_FINISH : LZMA_RUN);
0134     }
0135 
0136 protected:
0137     lzma_stream mStream = LZMA_STREAM_INIT;
0138 };
0139 
0140 CompressionStream::CompressionStream(QIODevice *stream, QObject *parent)
0141     : QIODevice(parent)
0142     , mStream(stream)
0143     , mResult(LZMA_OK)
0144 {
0145 }
0146 
0147 CompressionStream::~CompressionStream()
0148 {
0149     CompressionStream::close();
0150 }
0151 
0152 bool CompressionStream::isSequential() const
0153 {
0154     return true;
0155 }
0156 
0157 bool CompressionStream::open(OpenMode mode)
0158 {
0159     if ((mode & QIODevice::ReadOnly) && (mode & QIODevice::WriteOnly)) {
0160         qCWarning(AKONADIPRIVATE_LOG) << "Invalid open mode for CompressionStream.";
0161         return false;
0162     }
0163 
0164     mCompressor = std::make_unique<Compressor>();
0165     if (const auto err = mCompressor->initialize(mode & QIODevice::ReadOnly ? QIODevice::ReadOnly : QIODevice::WriteOnly); err != LZMA_OK) {
0166         qCWarning(AKONADIPRIVATE_LOG) << "Failed to initialize LZMA stream coder:" << err.message();
0167         return false;
0168     }
0169 
0170     if (mode & QIODevice::WriteOnly) {
0171         mBuffer.resize(BUFSIZ);
0172         mCompressor->setOutputBuffer(mBuffer.data(), mBuffer.size());
0173     }
0174 
0175     return QIODevice::open(mode);
0176 }
0177 
0178 void CompressionStream::close()
0179 {
0180     if (!isOpen()) {
0181         return;
0182     }
0183 
0184     if (openMode() & QIODevice::WriteOnly && mResult == LZMA_OK) {
0185         write(nullptr, 0);
0186     }
0187 
0188     mResult = mCompressor->finalize();
0189 
0190     setOpenMode(QIODevice::NotOpen);
0191 }
0192 
0193 std::error_code CompressionStream::error() const
0194 {
0195     return mResult == LZMA_STREAM_END ? LZMA_OK : mResult;
0196 }
0197 
0198 bool CompressionStream::atEnd() const
0199 {
0200     return mResult == LZMA_STREAM_END && QIODevice::atEnd() && mStream->atEnd();
0201 }
0202 
0203 qint64 CompressionStream::readData(char *data, qint64 dataSize)
0204 {
0205     qint64 dataRead = 0;
0206 
0207     if (mResult == LZMA_STREAM_END) {
0208         return 0;
0209     } else if (mResult != LZMA_OK) {
0210         return -1;
0211     }
0212 
0213     mCompressor->setOutputBuffer(data, dataSize);
0214 
0215     while (dataSize > 0) {
0216         if (mCompressor->inputBufferAvailable() == 0) {
0217             mBuffer.resize(BUFSIZ);
0218             const auto compressedDataRead = mStream->read(mBuffer.data(), mBuffer.size());
0219 
0220             if (compressedDataRead > 0) {
0221                 mCompressor->setInputBuffer(mBuffer.data(), compressedDataRead);
0222             } else {
0223                 break;
0224             }
0225         }
0226 
0227         mResult = mCompressor->inflate();
0228 
0229         if (mResult != LZMA_OK && mResult != LZMA_STREAM_END) {
0230             qCWarning(AKONADIPRIVATE_LOG) << "Error while decompressing LZMA stream:" << mResult.message();
0231             break;
0232         }
0233 
0234         const auto decompressedDataRead = dataSize - mCompressor->outputBufferAvailable();
0235         dataRead += decompressedDataRead;
0236         dataSize -= decompressedDataRead;
0237 
0238         if (mResult == LZMA_STREAM_END) {
0239             if (mStream->atEnd()) {
0240                 break;
0241             }
0242         }
0243 
0244         mCompressor->setOutputBuffer(data + dataRead, dataSize);
0245     }
0246 
0247     return dataRead;
0248 }
0249 
0250 qint64 CompressionStream::writeData(const char *data, qint64 dataSize)
0251 {
0252     if (mResult != LZMA_OK) {
0253         return 0;
0254     }
0255 
0256     bool finish = (data == nullptr);
0257     if (!finish) {
0258         mCompressor->setInputBuffer(data, dataSize);
0259     }
0260 
0261     size_t dataWritten = 0;
0262 
0263     while (dataSize > 0 || finish) {
0264         mResult = mCompressor->deflate(finish);
0265 
0266         if (mResult != LZMA_OK && mResult != LZMA_STREAM_END) {
0267             qCWarning(AKONADIPRIVATE_LOG) << "Error while compressing LZMA stream:" << mResult.message();
0268             break;
0269         }
0270 
0271         if (mCompressor->inputBufferAvailable() == 0 || (mResult == LZMA_STREAM_END)) {
0272             const auto wrote = dataSize - mCompressor->inputBufferAvailable();
0273 
0274             dataWritten += wrote;
0275             dataSize -= wrote;
0276 
0277             if (dataSize > 0) {
0278                 mCompressor->setInputBuffer(data + dataWritten, dataSize);
0279             }
0280         }
0281 
0282         if (mCompressor->outputBufferAvailable() == 0 || (mResult == LZMA_STREAM_END) || finish) {
0283             const auto toWrite = mBuffer.size() - mCompressor->outputBufferAvailable();
0284             if (toWrite > 0) {
0285                 const auto writtenSize = mStream->write(mBuffer.constData(), toWrite);
0286                 if (writtenSize != toWrite) {
0287                     qCWarning(AKONADIPRIVATE_LOG) << "Failed to write compressed data to output device:" << mStream->errorString();
0288                     setErrorString(QStringLiteral("Failed to write compressed data to output device."));
0289                     return 0;
0290                 }
0291             }
0292 
0293             if (mResult == LZMA_STREAM_END) {
0294                 Q_ASSERT(finish);
0295                 break;
0296             }
0297             mBuffer.resize(BUFSIZ);
0298             mCompressor->setOutputBuffer(mBuffer.data(), mBuffer.size());
0299         }
0300     }
0301 
0302     return dataWritten;
0303 }
0304 
0305 bool CompressionStream::isCompressed(QIODevice *data)
0306 {
0307     constexpr std::array<uchar, 6> magic = {0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00};
0308 
0309     if (!data->isOpen() && !data->isReadable()) {
0310         return false;
0311     }
0312 
0313     char buf[6] = {};
0314     if (data->peek(buf, sizeof(buf)) != sizeof(buf)) {
0315         return false;
0316     }
0317 
0318     return memcmp(magic.data(), buf, sizeof(buf)) == 0;
0319 }
0320 
0321 #include "moc_compressionstream_p.cpp"