File indexing completed on 2024-11-10 04:40:47
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"