2022-10-17 16:36:39 +03:00
|
|
|
/* This file is part of the KDE libraries
|
|
|
|
Copyright (C) 2022 Ivailo Monev <xakepa10@gmail.com>
|
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
|
|
modify it under the terms of the GNU Library General Public
|
|
|
|
License version 2, as published by the Free Software Foundation.
|
|
|
|
|
|
|
|
This library 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
|
|
|
|
Library General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Library General Public License
|
|
|
|
along with this library; see the file COPYING.LIB. If not, write to
|
|
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
|
|
Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
|
2022-10-30 05:41:21 +02:00
|
|
|
#include "config.h"
|
2022-10-17 16:36:39 +03:00
|
|
|
#include "kdecompressor.h"
|
|
|
|
#include "klocale.h"
|
|
|
|
#include "kmimetype.h"
|
|
|
|
#include "kdebug.h"
|
|
|
|
|
|
|
|
#include <limits.h>
|
|
|
|
#include <libdeflate.h>
|
|
|
|
|
2022-10-18 04:40:28 +03:00
|
|
|
#if defined(HAVE_BZIP2)
|
2022-10-17 16:36:39 +03:00
|
|
|
# include <bzlib.h>
|
|
|
|
#endif
|
|
|
|
|
2022-10-18 04:40:28 +03:00
|
|
|
#if defined(HAVE_LIBLZMA)
|
2022-10-17 16:36:39 +03:00
|
|
|
# include <lzma.h>
|
|
|
|
#endif
|
|
|
|
|
2022-12-14 09:54:10 +02:00
|
|
|
#define KDECOMPRESSOR_BUFFSIZE 1024 * 1000 // 1MB
|
2024-03-22 12:18:41 +02:00
|
|
|
// the limit of QByteArray
|
|
|
|
#define KDECOMPRESSOR_BUFFMAX INT_MAX
|
2022-12-14 09:54:10 +02:00
|
|
|
|
2022-10-17 16:36:39 +03:00
|
|
|
class KDecompressorPrivate
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
KDecompressorPrivate();
|
|
|
|
|
|
|
|
KDecompressor::KDecompressorType m_type;
|
|
|
|
QByteArray m_result;
|
|
|
|
QString m_errorstring;
|
|
|
|
};
|
|
|
|
|
|
|
|
KDecompressorPrivate::KDecompressorPrivate()
|
|
|
|
: m_type(KDecompressor::TypeUnknown)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
KDecompressor::KDecompressor()
|
|
|
|
: d(new KDecompressorPrivate())
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
KDecompressor::~KDecompressor()
|
|
|
|
{
|
|
|
|
delete d;
|
|
|
|
}
|
|
|
|
|
|
|
|
KDecompressor::KDecompressorType KDecompressor::type() const
|
|
|
|
{
|
|
|
|
return d->m_type;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KDecompressor::setType(const KDecompressorType type)
|
|
|
|
{
|
|
|
|
if (type == KDecompressor::TypeUnknown) {
|
2022-10-17 20:12:52 +03:00
|
|
|
d->m_errorstring = i18n("Invalid type: %1", int(type));
|
|
|
|
return false;
|
|
|
|
}
|
2022-10-18 04:40:28 +03:00
|
|
|
#if !defined(HAVE_BZIP2)
|
2022-10-17 20:12:52 +03:00
|
|
|
if (type == KCompressor::TypeBZip2) {
|
|
|
|
d->m_errorstring = i18n("Unsupported type: %1", int(type));
|
2022-10-17 16:36:39 +03:00
|
|
|
return false;
|
|
|
|
}
|
2022-10-17 20:12:52 +03:00
|
|
|
#endif
|
2022-10-18 04:40:28 +03:00
|
|
|
#if !defined(HAVE_LIBLZMA)
|
2022-10-17 20:12:52 +03:00
|
|
|
if (type == KCompressor::TypeXZ) {
|
|
|
|
d->m_errorstring = i18n("Unsupported type: %1", int(type));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
#endif
|
2024-03-23 08:51:49 +02:00
|
|
|
d->m_errorstring.clear();
|
2022-10-17 16:36:39 +03:00
|
|
|
d->m_type = type;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KDecompressor::process(const QByteArray &data)
|
|
|
|
{
|
|
|
|
d->m_errorstring.clear();
|
|
|
|
|
|
|
|
switch (d->m_type) {
|
|
|
|
case KDecompressor::TypeUnknown: {
|
2022-10-19 18:44:22 +03:00
|
|
|
d->m_errorstring = i18n("Invalid type: %1", int(d->m_type));
|
2024-03-22 12:25:43 +02:00
|
|
|
d->m_result.clear();
|
2022-10-17 16:36:39 +03:00
|
|
|
return false;
|
|
|
|
}
|
2022-10-19 18:45:34 +03:00
|
|
|
case KDecompressor::TypeDeflate:
|
|
|
|
case KDecompressor::TypeZlib:
|
2022-10-17 16:36:39 +03:00
|
|
|
case KDecompressor::TypeGZip: {
|
2024-03-23 08:51:49 +02:00
|
|
|
size_t speculativesize = (data.size() * 2 + KDECOMPRESSOR_BUFFSIZE);
|
2024-03-22 12:18:41 +02:00
|
|
|
if (Q_UNLIKELY(speculativesize >= KDECOMPRESSOR_BUFFMAX)) {
|
2024-03-21 19:10:31 +02:00
|
|
|
d->m_errorstring = i18n("Input data size too big: %1", data.size());
|
2024-03-22 12:25:43 +02:00
|
|
|
d->m_result.clear();
|
2024-03-21 19:10:31 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
d->m_result.resize(speculativesize);
|
|
|
|
|
2022-10-17 16:36:39 +03:00
|
|
|
struct libdeflate_decompressor* decomp = libdeflate_alloc_decompressor();
|
|
|
|
if (Q_UNLIKELY(!decomp)) {
|
|
|
|
d->m_errorstring = i18n("Could not allocate decompressor");
|
2024-03-22 12:25:43 +02:00
|
|
|
d->m_result.clear();
|
2022-10-17 16:36:39 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
libdeflate_result decompresult = LIBDEFLATE_INSUFFICIENT_SPACE;
|
|
|
|
while (decompresult == LIBDEFLATE_INSUFFICIENT_SPACE) {
|
2022-10-19 18:45:34 +03:00
|
|
|
switch (d->m_type) {
|
|
|
|
case KDecompressor::TypeDeflate: {
|
|
|
|
decompresult = libdeflate_deflate_decompress(
|
|
|
|
decomp,
|
|
|
|
data.constData(), data.size(),
|
|
|
|
d->m_result.data(), d->m_result.size(),
|
|
|
|
&speculativesize
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case KDecompressor::TypeZlib: {
|
|
|
|
decompresult = libdeflate_zlib_decompress(
|
|
|
|
decomp,
|
|
|
|
data.constData(), data.size(),
|
|
|
|
d->m_result.data(), d->m_result.size(),
|
|
|
|
&speculativesize
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case KDecompressor::TypeGZip: {
|
|
|
|
decompresult = libdeflate_gzip_decompress(
|
|
|
|
decomp,
|
|
|
|
data.constData(), data.size(),
|
|
|
|
d->m_result.data(), d->m_result.size(),
|
|
|
|
&speculativesize
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
2022-10-24 16:54:31 +03:00
|
|
|
default: {
|
|
|
|
// shush compiler
|
|
|
|
Q_ASSERT(false);
|
|
|
|
break;
|
|
|
|
}
|
2022-10-19 18:45:34 +03:00
|
|
|
}
|
2022-10-17 16:36:39 +03:00
|
|
|
|
|
|
|
if (decompresult == LIBDEFLATE_INSUFFICIENT_SPACE) {
|
2022-12-14 09:54:10 +02:00
|
|
|
speculativesize = (speculativesize + KDECOMPRESSOR_BUFFSIZE);
|
2024-03-22 12:18:41 +02:00
|
|
|
if (speculativesize >= KDECOMPRESSOR_BUFFMAX) {
|
2024-03-21 19:10:31 +02:00
|
|
|
break;
|
|
|
|
}
|
2022-10-17 16:36:39 +03:00
|
|
|
d->m_result.resize(speculativesize);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
libdeflate_free_decompressor(decomp);
|
|
|
|
|
2022-10-17 18:51:33 +03:00
|
|
|
if (Q_UNLIKELY(decompresult != LIBDEFLATE_SUCCESS)) {
|
2022-10-17 16:36:39 +03:00
|
|
|
d->m_errorstring = i18n("Could not decompress data");
|
|
|
|
d->m_result.clear();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
d->m_result.resize(speculativesize);
|
|
|
|
return true;
|
|
|
|
}
|
2022-10-18 04:40:28 +03:00
|
|
|
#if defined(HAVE_BZIP2)
|
2022-10-17 16:36:39 +03:00
|
|
|
case KDecompressor::TypeBZip2: {
|
2024-03-23 08:51:49 +02:00
|
|
|
uint speculativesize = (data.size() * 2 + KDECOMPRESSOR_BUFFSIZE);
|
2024-03-22 12:18:41 +02:00
|
|
|
if (Q_UNLIKELY(speculativesize >= KDECOMPRESSOR_BUFFMAX)) {
|
2024-03-21 19:10:31 +02:00
|
|
|
d->m_errorstring = i18n("Input data size too big: %1", data.size());
|
2024-03-22 12:25:43 +02:00
|
|
|
d->m_result.clear();
|
2024-03-21 19:10:31 +02:00
|
|
|
return false;
|
|
|
|
}
|
2022-10-17 16:36:39 +03:00
|
|
|
d->m_result.resize(speculativesize);
|
2022-10-17 22:42:53 +03:00
|
|
|
|
2022-10-17 16:36:39 +03:00
|
|
|
int decompresult = BZ_OUTBUFF_FULL;
|
|
|
|
while (decompresult == BZ_OUTBUFF_FULL) {
|
|
|
|
decompresult = BZ2_bzBuffToBuffDecompress(
|
|
|
|
d->m_result.data(), &speculativesize,
|
|
|
|
(char*)data.constData(), data.size(),
|
|
|
|
0, 0
|
|
|
|
);
|
|
|
|
|
|
|
|
if (decompresult == BZ_OUTBUFF_FULL) {
|
2022-12-14 09:54:10 +02:00
|
|
|
speculativesize = (speculativesize + KDECOMPRESSOR_BUFFSIZE);
|
2024-03-22 12:18:41 +02:00
|
|
|
if (speculativesize >= KDECOMPRESSOR_BUFFMAX) {
|
2024-03-21 19:10:31 +02:00
|
|
|
break;
|
|
|
|
}
|
2022-10-17 16:36:39 +03:00
|
|
|
d->m_result.resize(speculativesize);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Q_UNLIKELY(decompresult < BZ_OK || decompresult > BZ_STREAM_END)) {
|
|
|
|
d->m_errorstring = i18n("Could not decompress data");
|
|
|
|
d->m_result.clear();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
d->m_result.resize(speculativesize);
|
|
|
|
return true;
|
|
|
|
}
|
2022-10-18 04:40:28 +03:00
|
|
|
#endif // HAVE_BZIP2
|
|
|
|
#if defined(HAVE_LIBLZMA)
|
2022-10-17 16:36:39 +03:00
|
|
|
case KDecompressor::TypeXZ: {
|
2024-03-23 08:51:49 +02:00
|
|
|
size_t speculativesize = (data.size() * 2 + KDECOMPRESSOR_BUFFSIZE);
|
2024-03-22 12:18:41 +02:00
|
|
|
if (Q_UNLIKELY(speculativesize >= KDECOMPRESSOR_BUFFMAX)) {
|
2024-03-21 19:10:31 +02:00
|
|
|
d->m_errorstring = i18n("Input data size too big: %1", data.size());
|
2024-03-22 12:25:43 +02:00
|
|
|
d->m_result.clear();
|
2024-03-21 19:10:31 +02:00
|
|
|
return false;
|
|
|
|
}
|
2022-10-17 16:36:39 +03:00
|
|
|
d->m_result.resize(speculativesize);
|
|
|
|
|
|
|
|
lzma_stream decomp = LZMA_STREAM_INIT;
|
|
|
|
decomp.next_in = (const uint8_t*)data.constData();
|
|
|
|
decomp.avail_in = data.size();
|
|
|
|
decomp.next_out = (uint8_t*)d->m_result.data();
|
|
|
|
decomp.avail_out = speculativesize;
|
|
|
|
|
|
|
|
lzma_ret decompresult = lzma_auto_decoder(&decomp, UINT64_MAX, 0);
|
|
|
|
if (Q_UNLIKELY(decompresult != LZMA_OK)) {
|
|
|
|
d->m_errorstring = i18n("Could not initialize decompressor");
|
|
|
|
d->m_result.clear();
|
2022-10-17 23:09:15 +03:00
|
|
|
lzma_end(&decomp);
|
2022-10-17 16:36:39 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-10-17 22:42:53 +03:00
|
|
|
decompresult = LZMA_BUF_ERROR;
|
2022-10-17 23:30:37 +03:00
|
|
|
while (decompresult != LZMA_STREAM_END) {
|
2022-10-17 16:36:39 +03:00
|
|
|
decompresult = lzma_code(&decomp, LZMA_FINISH);
|
|
|
|
|
2022-10-17 22:42:53 +03:00
|
|
|
if (decompresult == LZMA_BUF_ERROR) {
|
2022-12-14 09:54:10 +02:00
|
|
|
speculativesize = (speculativesize + KDECOMPRESSOR_BUFFSIZE);
|
2024-03-22 12:18:41 +02:00
|
|
|
if (speculativesize >= KDECOMPRESSOR_BUFFMAX) {
|
2022-10-18 21:00:42 +03:00
|
|
|
break;
|
|
|
|
}
|
2024-03-21 19:10:31 +02:00
|
|
|
d->m_result.resize(speculativesize);
|
|
|
|
decomp.next_out = (uint8_t*)d->m_result.data();
|
|
|
|
decomp.avail_out = speculativesize;
|
2022-10-17 23:30:37 +03:00
|
|
|
} else if (decompresult != LZMA_OK) {
|
|
|
|
break;
|
2022-10-17 16:36:39 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Q_UNLIKELY(decompresult != LZMA_OK && decompresult != LZMA_STREAM_END)) {
|
|
|
|
d->m_errorstring = i18n("Could not decompress data");
|
|
|
|
d->m_result.clear();
|
2022-10-17 23:09:15 +03:00
|
|
|
lzma_end(&decomp);
|
2022-10-17 16:36:39 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
speculativesize = decomp.total_out;
|
|
|
|
lzma_end(&decomp);
|
|
|
|
|
|
|
|
d->m_result.resize(speculativesize);
|
|
|
|
return true;
|
|
|
|
}
|
2022-10-18 04:40:28 +03:00
|
|
|
#endif // HAVE_LIBLZMA
|
2022-10-17 16:36:39 +03:00
|
|
|
default: {
|
2022-10-17 20:12:52 +03:00
|
|
|
d->m_errorstring = i18n("Unsupported type: %1", int(d->m_type));
|
2024-03-22 12:25:43 +02:00
|
|
|
d->m_result.clear();
|
2022-10-17 16:36:39 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Q_UNREACHABLE();
|
|
|
|
}
|
|
|
|
|
|
|
|
QByteArray KDecompressor::result() const
|
|
|
|
{
|
|
|
|
return d->m_result;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString KDecompressor::errorString() const
|
|
|
|
{
|
|
|
|
return d->m_errorstring;
|
|
|
|
}
|
|
|
|
|
|
|
|
KDecompressor::KDecompressorType KDecompressor::typeForMime(const QString &mime)
|
|
|
|
{
|
|
|
|
const KMimeType::Ptr kmimetype = KMimeType::mimeType(mime);
|
|
|
|
if (kmimetype) {
|
|
|
|
if (kmimetype->is(QString::fromLatin1("application/x-gzip"))) {
|
|
|
|
return KDecompressor::TypeGZip;
|
|
|
|
} else if (kmimetype->is(QString::fromLatin1("application/x-bzip"))) {
|
|
|
|
return KDecompressor::TypeBZip2;
|
|
|
|
} else if (kmimetype->is(QString::fromLatin1("application/x-xz"))) {
|
|
|
|
return KDecompressor::TypeXZ;
|
2022-10-18 00:47:45 +03:00
|
|
|
// lzma_auto_decoder() should detect the filter for it
|
|
|
|
} else if (kmimetype->is(QString::fromLatin1("application/x-lzma"))) {
|
|
|
|
return KDecompressor::TypeXZ;
|
2022-10-17 16:36:39 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return KDecompressor::TypeUnknown;
|
|
|
|
}
|
|
|
|
|
|
|
|
KDecompressor::KDecompressorType KDecompressor::typeForFile(const QString &filepath)
|
|
|
|
{
|
|
|
|
const KMimeType::Ptr kmimetype = KMimeType::findByPath(filepath);
|
|
|
|
if (kmimetype) {
|
|
|
|
return KDecompressor::typeForMime(kmimetype->name());
|
|
|
|
}
|
|
|
|
return KDecompressor::TypeUnknown;
|
|
|
|
}
|