Advance Wayland and KDE package bring-up

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-14 10:51:06 +01:00
parent 51f3c21121
commit cf12defd28
15214 changed files with 20594243 additions and 269 deletions
@@ -0,0 +1,145 @@
set(HAVE_BZIP2_SUPPORT ${BZIP2_FOUND})
if(BZIP2_FOUND AND BZIP2_NEED_PREFIX)
set(NEED_BZ2_PREFIX 1)
endif()
set(HAVE_XZ_SUPPORT ${LIBLZMA_FOUND})
set(HAVE_ZSTD_SUPPORT ${LibZstd_FOUND})
configure_file(config-compression.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-compression.h)
add_library(KF6Archive)
add_library(KF6::Archive ALIAS KF6Archive)
set_target_properties(KF6Archive PROPERTIES
VERSION ${KARCHIVE_VERSION}
SOVERSION ${KARCHIVE_SOVERSION}
EXPORT_NAME "Archive"
)
ecm_create_qm_loader(KF6Archive karchive6_qt)
if(BZIP2_FOUND)
target_sources(KF6Archive PRIVATE kbzip2filter.cpp)
target_link_libraries(KF6Archive PRIVATE BZip2::BZip2)
endif()
if(LIBLZMA_FOUND)
target_sources(KF6Archive PRIVATE kxzfilter.cpp k7zip.cpp)
target_link_libraries(KF6Archive PRIVATE LibLZMA::LibLZMA)
endif()
if (LibZstd_FOUND)
target_sources(KF6Archive PRIVATE kzstdfilter.cpp)
target_link_libraries(KF6Archive PRIVATE PkgConfig::LibZstd)
endif()
target_sources(KF6Archive PRIVATE karchive.cpp
kar.cpp
kcompressiondevice.cpp
kfilterbase.cpp
kgzipfilter.cpp
klimitediodevice.cpp
knonefilter.cpp
ktar.cpp
kzip.cpp
krcc.cpp
)
ecm_qt_declare_logging_category(KF6Archive
HEADER loggingcategory.h
IDENTIFIER KArchiveLog
CATEGORY_NAME kf.archive
OLD_CATEGORY_NAMES kf5.karchive
DEFAULT_SEVERITY Warning
DESCRIPTION "KArchive"
EXPORT KARCHIVE
)
ecm_generate_export_header(KF6Archive
BASE_NAME KArchive
GROUP_BASE_NAME KF
VERSION ${KF_VERSION}
USE_VERSION_HEADER
DEPRECATED_BASE_VERSION 0
DEPRECATION_VERSIONS
EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT}
)
target_include_directories(KF6Archive
INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/KArchive>"
)
target_link_libraries(KF6Archive
PUBLIC
Qt6::Core
PRIVATE
ZLIB::ZLIB
)
ecm_generate_headers(KArchive_HEADERS
HEADER_NAMES
KArchive
KArchiveEntry
KArchiveFile
KArchiveDirectory
KAr
KCompressionDevice
KFilterBase
KRcc
KTar
KZip
KZipFileEntry
REQUIRED_HEADERS KArchive_HEADERS
)
install(TARGETS KF6Archive
EXPORT KF6ArchiveTargets
${KF_INSTALL_TARGETS_DEFAULT_ARGS})
if(LIBLZMA_FOUND)
ecm_generate_headers(KArchive_HEADERS
HEADER_NAMES
K7Zip
REQUIRED_HEADERS KArchive_HEADERS
)
endif()
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/karchive_export.h
${KArchive_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KArchive
COMPONENT Devel)
ecm_qt_install_logging_categories(
EXPORT KARCHIVE
FILE karchive.categories
DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}
)
if(BUILD_QCH)
ecm_add_qch(
KF6Archive_QCH
NAME KArchive
BASE_NAME KF6Archive
VERSION ${KF_VERSION}
ORG_DOMAIN org.kde
SOURCES # using only public headers, to cover only public API
${KArchive_HEADERS}
MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md"
LINK_QCHS
Qt6Core_QCH
INCLUDE_DIRS
${CMAKE_CURRENT_BINARY_DIR}
BLANK_MACROS
KARCHIVE_EXPORT
KARCHIVE_DEPRECATED
"KARCHIVE_DEPRECATED_VERSION(x, y, t)"
TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
COMPONENT Devel
)
endif()
+6
View File
@@ -0,0 +1,6 @@
#!/bin/sh
# Extract strings from all source files.
# EXTRACT_TR_STRINGS extracts strings with lupdate and convert them to .pot with
# lconvert.
$EXTRACT_TR_STRINGS `find . -name \*.cpp -o -name \*.h -o -name \*.ui -o -name \*.qml` -o $podir/karchive6_qt.pot
@@ -0,0 +1,11 @@
#cmakedefine01 HAVE_BZIP2_SUPPORT
/* Set to 1 if the libbz2 functions need the BZ2_ prefix */
#cmakedefine01 NEED_BZ2_PREFIX
/* Set to 1 if you have xz */
#cmakedefine01 HAVE_XZ_SUPPORT
/* Set to 1 if you have zstd */
#cmakedefine01 HAVE_ZSTD_SUPPORT
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,97 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2011 Mario Bensi <mbensi@ipsquad.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef K7ZIP_H
#define K7ZIP_H
#include <karchive.h>
/**
* @class K7Zip k7zip.h K7Zip
*
* A class for reading / writing p7zip archives.
*
* @author Mario Bensi
*/
class KARCHIVE_EXPORT K7Zip : public KArchive
{
Q_DECLARE_TR_FUNCTIONS(K7Zip)
public:
/**
* Creates an instance that operates on the given filename
* using the compression filter associated to given mimetype.
*
* @param filename is a local path (e.g. "/home/user/myfile.7z")
*/
explicit K7Zip(const QString &filename);
/**
* Creates an instance that operates on the given device.
* The device can be compressed (KCompressionDevice) or not (QFile, etc.).
* @warning Do not assume that giving a QFile here will decompress the file,
* in case it's compressed!
* @param dev the device to read from. If the source is compressed, the
* QIODevice must take care of decompression
*/
explicit K7Zip(QIODevice *dev);
/**
* If the archive is still opened, then it will be
* closed automatically by the destructor.
*/
~K7Zip() override;
protected:
/// Reimplemented from KArchive
bool doWriteSymLink(const QString &name,
const QString &target,
const QString &user,
const QString &group,
mode_t perm,
const QDateTime &atime,
const QDateTime &mtime,
const QDateTime &ctime) override;
/// Reimplemented from KArchive
bool doWriteDir(const QString &name,
const QString &user,
const QString &group,
mode_t perm,
const QDateTime &atime,
const QDateTime &mtime,
const QDateTime &ctime) override;
/// Reimplemented from KArchive
bool doPrepareWriting(const QString &name,
const QString &user,
const QString &group,
qint64 size,
mode_t perm,
const QDateTime &atime,
const QDateTime &mtime,
const QDateTime &ctime) override;
/// Reimplemented from KArchive
bool doFinishWriting(qint64 size) override;
/// Reimplemented from KArchive
bool doWriteData(const char *data, qint64 size) override;
/**
* Opens the archive for reading.
* Parses the directory listing of the archive
* and creates the KArchiveDirectory/KArchiveFile entries.
* @param mode the mode of the file
*/
bool openArchive(QIODevice::OpenMode mode) override;
bool closeArchive() override;
protected:
void virtual_hook(int id, void *data) override;
private:
class K7ZipPrivate;
K7ZipPrivate *const d;
};
#endif
@@ -0,0 +1,195 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2002 Laurence Anderson <l.d.anderson@warwick.ac.uk>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kar.h"
#include "karchive_p.h"
#include "loggingcategory.h"
#include <QDebug>
#include <QFile>
#include <limits>
#include "kcompressiondevice.h"
//#include "klimitediodevice_p.h"
// As documented in QByteArray
static constexpr int kMaxQByteArraySize = std::numeric_limits<int>::max() - 32;
////////////////////////////////////////////////////////////////////////
/////////////////////////// KAr ///////////////////////////////////////
////////////////////////////////////////////////////////////////////////
class Q_DECL_HIDDEN KAr::KArPrivate
{
public:
KArPrivate()
{
}
};
KAr::KAr(const QString &filename)
: KArchive(filename)
, d(new KArPrivate)
{
}
KAr::KAr(QIODevice *dev)
: KArchive(dev)
, d(new KArPrivate)
{
}
KAr::~KAr()
{
if (isOpen()) {
close();
}
delete d;
}
bool KAr::doPrepareWriting(const QString &, const QString &, const QString &, qint64, mode_t, const QDateTime &, const QDateTime &, const QDateTime &)
{
setErrorString(tr("Cannot write to AR file"));
qCWarning(KArchiveLog) << "doPrepareWriting not implemented for KAr";
return false;
}
bool KAr::doFinishWriting(qint64)
{
setErrorString(tr("Cannot write to AR file"));
qCWarning(KArchiveLog) << "doFinishWriting not implemented for KAr";
return false;
}
bool KAr::doWriteDir(const QString &, const QString &, const QString &, mode_t, const QDateTime &, const QDateTime &, const QDateTime &)
{
setErrorString(tr("Cannot write to AR file"));
qCWarning(KArchiveLog) << "doWriteDir not implemented for KAr";
return false;
}
bool KAr::doWriteSymLink(const QString &, const QString &, const QString &, const QString &, mode_t, const QDateTime &, const QDateTime &, const QDateTime &)
{
setErrorString(tr("Cannot write to AR file"));
qCWarning(KArchiveLog) << "doWriteSymLink not implemented for KAr";
return false;
}
bool KAr::openArchive(QIODevice::OpenMode mode)
{
// Open archive
if (mode == QIODevice::WriteOnly) {
return true;
}
if (mode != QIODevice::ReadOnly && mode != QIODevice::ReadWrite) {
setErrorString(tr("Unsupported mode %1").arg(static_cast<int>(mode)));
return false;
}
QIODevice *dev = device();
if (!dev) {
return false;
}
QByteArray magic = dev->read(7);
if (magic != "!<arch>") {
setErrorString(tr("Invalid main magic"));
return false;
}
QByteArray ar_longnames;
while (!dev->atEnd()) {
QByteArray ar_header;
ar_header.resize(60);
dev->seek(dev->pos() + (2 - (dev->pos() % 2)) % 2); // Ar headers are padded to byte boundary
if (dev->read(ar_header.data(), 60) != 60) { // Read ar header
qCWarning(KArchiveLog) << "Couldn't read header";
return true; // Probably EOF / trailing junk
}
if (!ar_header.endsWith("`\n")) { // Check header magic // krazy:exclude=strings
setErrorString(tr("Invalid magic"));
return false;
}
QByteArray name = ar_header.mid(0, 16); // Process header
const int date = ar_header.mid(16, 12).trimmed().toInt();
// const int uid = ar_header.mid( 28, 6 ).trimmed().toInt();
// const int gid = ar_header.mid( 34, 6 ).trimmed().toInt();
const int mode = ar_header.mid(40, 8).trimmed().toInt(nullptr, 8);
const qint64 size = ar_header.mid(48, 10).trimmed().toInt();
if (size < 0 || size > kMaxQByteArraySize) {
setErrorString(tr("Invalid size"));
return false;
}
bool skip_entry = false; // Deal with special entries
if (name.mid(0, 1) == "/") {
if (name.mid(1, 1) == "/") { // Longfilename table entry
ar_longnames.resize(size);
// Read the table. Note that the QByteArray will contain NUL characters after each entry.
dev->read(ar_longnames.data(), size);
skip_entry = true;
qCDebug(KArchiveLog) << "Read in longnames entry";
} else if (name.mid(1, 1) == " ") { // Symbol table entry
qCDebug(KArchiveLog) << "Skipped symbol entry";
dev->seek(dev->pos() + size);
skip_entry = true;
} else { // Longfilename, look it up in the table
const int ar_longnamesIndex = name.mid(1, 15).trimmed().toInt();
qCDebug(KArchiveLog) << "Longfilename #" << ar_longnamesIndex;
if (ar_longnames.isEmpty()) {
setErrorString(tr("Invalid longfilename reference"));
return false;
}
if (ar_longnamesIndex < 0 || ar_longnamesIndex >= ar_longnames.size()) {
setErrorString(tr("Invalid longfilename position reference"));
return false;
}
name = QByteArray(ar_longnames.constData() + ar_longnamesIndex);
name.truncate(name.indexOf('/'));
}
}
if (skip_entry) {
continue;
}
// Process filename
name = name.trimmed();
name.replace('/', QByteArray());
qCDebug(KArchiveLog) << "Filename: " << name << " Size: " << size;
KArchiveEntry *entry = new KArchiveFile(this,
QString::fromLocal8Bit(name.constData()),
mode,
KArchivePrivate::time_tToDateTime(date),
rootDir()->user(),
rootDir()->group(),
/*symlink*/ QString(),
dev->pos(),
size);
rootDir()->addEntry(entry); // Ar files don't support directories, so everything in root
dev->seek(dev->pos() + size); // Skip contents
}
return true;
}
bool KAr::closeArchive()
{
// Close the archive
return true;
}
void KAr::virtual_hook(int id, void *data)
{
KArchive::virtual_hook(id, data);
}
@@ -0,0 +1,103 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2002 Laurence Anderson <l.d.anderson@warwick.ac.uk>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KAR_H
#define KAR_H
#include <karchive.h>
/**
* @class KAr kar.h KAr
*
* KAr is a class for reading archives in ar format. Writing
* is not supported. Reading archives that contain files bigger than
* INT_MAX - 32 bytes is not supported.
* @short A class for reading ar archives.
* @author Laurence Anderson <l.d.anderson@warwick.ac.uk>
*/
class KARCHIVE_EXPORT KAr : public KArchive
{
Q_DECLARE_TR_FUNCTIONS(KAr)
public:
/**
* Creates an instance that operates on the given filename.
*
* @param filename is a local path (e.g. "/home/holger/myfile.ar")
*/
explicit KAr(const QString &filename);
/**
* Creates an instance that operates on the given device.
* The device can be compressed (KCompressionDevice) or not (QFile, etc.).
* @param dev the device to read from
*/
explicit KAr(QIODevice *dev);
/**
* If the ar file is still opened, then it will be
* closed automatically by the destructor.
*/
~KAr() override;
protected:
/*
* Writing is not supported by this class, will always fail.
* @return always false
*/
bool doPrepareWriting(const QString &name,
const QString &user,
const QString &group,
qint64 size,
mode_t perm,
const QDateTime &atime,
const QDateTime &mtime,
const QDateTime &ctime) override;
/*
* Writing is not supported by this class, will always fail.
* @return always false
*/
bool doFinishWriting(qint64 size) override;
/*
* Writing is not supported by this class, will always fail.
* @return always false
*/
bool doWriteDir(const QString &name,
const QString &user,
const QString &group,
mode_t perm,
const QDateTime &atime,
const QDateTime &mtime,
const QDateTime &ctime) override;
bool doWriteSymLink(const QString &name,
const QString &target,
const QString &user,
const QString &group,
mode_t perm,
const QDateTime &atime,
const QDateTime &mtime,
const QDateTime &ctime) override;
/**
* Opens the archive for reading.
* Parses the directory listing of the archive
* and creates the KArchiveDirectory/KArchiveFile entries.
*
*/
bool openArchive(QIODevice::OpenMode mode) override;
bool closeArchive() override;
protected:
void virtual_hook(int id, void *data) override;
private:
class KArPrivate;
KArPrivate *const d;
};
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,433 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000-2005 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2003 Leo Savernik <l.savernik@aon.at>
Moved from ktar.h by Roberto Teixeira <maragato@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KARCHIVE_H
#define KARCHIVE_H
#include <sys/stat.h>
#include <sys/types.h>
#include <QByteArrayView>
#include <QCoreApplication>
#include <QDate>
#include <QHash>
#include <QIODevice>
#include <QString>
#include <QStringList>
#include <karchive_export.h>
#ifdef Q_OS_WIN
#include <qplatformdefs.h> // mode_t
#endif
class KArchiveDirectory;
class KArchiveFile;
class KArchivePrivate;
/**
* @class KArchive karchive.h KArchive
*
* KArchive is a base class for reading and writing archives.
* @short generic class for reading/writing archives
* @author David Faure <faure@kde.org>
*/
class KARCHIVE_EXPORT KArchive
{
Q_DECLARE_TR_FUNCTIONS(KArchive)
protected:
/**
* Base constructor (protected since this is a pure virtual class).
* @param fileName is a local path (e.g. "/tmp/myfile.ext"),
* from which the archive will be read from, or into which the archive
* will be written, depending on the mode given to open().
*/
explicit KArchive(const QString &fileName);
/**
* Base constructor (protected since this is a pure virtual class).
* @param dev the I/O device where the archive reads its data
* Note that this can be a file, but also a data buffer, a compression filter, etc.
* For a file in writing mode it is better to use the other constructor
* though, to benefit from the use of QSaveFile when saving.
*/
explicit KArchive(QIODevice *dev);
public:
virtual ~KArchive();
/**
* Opens the archive for reading or writing.
* Inherited classes might want to reimplement openArchive instead.
* @param mode may be QIODevice::ReadOnly or QIODevice::WriteOnly
* @see close
*/
virtual bool open(QIODevice::OpenMode mode);
/**
* Closes the archive.
* Inherited classes might want to reimplement closeArchive instead.
*
* @return true if close succeeded without problems
* @see open
*/
virtual bool close();
/**
* Returns a description of the last error
* @since 5.29
*/
QString errorString() const;
/**
* Checks whether the archive is open.
* @return true if the archive is opened
*/
bool isOpen() const;
/**
* Returns the mode in which the archive was opened
* @return the mode in which the archive was opened (QIODevice::ReadOnly or QIODevice::WriteOnly)
* @see open()
*/
QIODevice::OpenMode mode() const;
/**
* The underlying device.
* @return the underlying device.
*/
QIODevice *device() const;
/**
* The name of the archive file, as passed to the constructor that takes a
* fileName, or an empty string if you used the QIODevice constructor.
* @return the name of the file, or QString() if unknown
*/
QString fileName() const;
/**
* If an archive is opened for reading, then the contents
* of the archive can be accessed via this function.
* @return the directory of the archive
*/
const KArchiveDirectory *directory() const;
/**
* Writes a local file into the archive. The main difference with writeFile,
* is that this method minimizes memory usage, by not loading the whole file
* into memory in one go.
*
* If @p fileName is a symbolic link, it will be written as is, i.e.
* it will not be resolved before.
* @param fileName full path to an existing local file, to be added to the archive.
* @param destName the resulting name (or relative path) of the file in the archive.
*/
bool addLocalFile(const QString &fileName, const QString &destName);
/**
* Writes a local directory into the archive, including all its contents, recursively.
* Calls addLocalFile for each file to be added.
*
* It will also add a @p path that is a symbolic link to a
* directory. The symbolic link will be dereferenced and the content of the
* directory it is pointing to added recursively. However, symbolic links
* *under* @p path will be stored as is.
* @param path full path to an existing local directory, to be added to the archive.
* @param destName the resulting name (or relative path) of the file in the archive.
*/
bool addLocalDirectory(const QString &path, const QString &destName);
/**
* If an archive is opened for writing then you can add new directories
* using this function. KArchive won't write one directory twice.
*
* This method also allows some file metadata to be set.
* However, depending on the archive type not all metadata might be regarded.
*
* @param name the name of the directory
* @param user the user that owns the directory
* @param group the group that owns the directory
* @param perm permissions of the directory
* @param atime time the file was last accessed
* @param mtime modification time of the file
* @param ctime time of last status change
*/
bool writeDir(const QString &name,
const QString &user = QString(),
const QString &group = QString(),
mode_t perm = 040755,
const QDateTime &atime = QDateTime(),
const QDateTime &mtime = QDateTime(),
const QDateTime &ctime = QDateTime());
/**
* Writes a symbolic link to the archive if supported.
* The archive must be opened for writing.
*
* @param name name of symbolic link
* @param target target of symbolic link
* @param user the user that owns the directory
* @param group the group that owns the directory
* @param perm permissions of the directory
* @param atime time the file was last accessed
* @param mtime modification time of the file
* @param ctime time of last status change
*/
bool writeSymLink(const QString &name,
const QString &target,
const QString &user = QString(),
const QString &group = QString(),
mode_t perm = 0120755,
const QDateTime &atime = QDateTime(),
const QDateTime &mtime = QDateTime(),
const QDateTime &ctime = QDateTime());
/**
* Writes a new file into the archive.
*
* The archive must be opened for writing first.
*
* The necessary parent directories are created automatically
* if needed. For instance, writing "mydir/test1" does not
* require creating the directory "mydir" first.
*
* This method also allows some file metadata to be
* set. However, depending on the archive type not all metadata might be
* written out.
*
* @param name the name of the file
* @param data the data to write
* @param perm permissions of the file
* @param user the user that owns the file
* @param group the group that owns the file
* @param atime time the file was last accessed
* @param mtime modification time of the file
* @param ctime time of last status change
* @since 6.0
*/
bool writeFile(const QString &name,
QByteArrayView data,
mode_t perm = 0100644,
const QString &user = QString(),
const QString &group = QString(),
const QDateTime &atime = QDateTime(),
const QDateTime &mtime = QDateTime(),
const QDateTime &ctime = QDateTime());
/**
* Here's another way of writing a file into an archive:
* Call prepareWriting(), then call writeData()
* as many times as wanted then call finishWriting( totalSize ).
* For tar.gz files, you need to know the size before hand, it is needed in the header!
* For zip files, size isn't used.
*
* This method also allows some file metadata to be
* set. However, depending on the archive type not all metadata might be
* regarded.
* @param name the name of the file
* @param user the user that owns the file
* @param group the group that owns the file
* @param size the size of the file
* @param perm permissions of the file
* @param atime time the file was last accessed
* @param mtime modification time of the file
* @param ctime time of last status change
*/
bool prepareWriting(const QString &name,
const QString &user,
const QString &group,
qint64 size,
mode_t perm = 0100644,
const QDateTime &atime = QDateTime(),
const QDateTime &mtime = QDateTime(),
const QDateTime &ctime = QDateTime());
/**
* Write data into the current file - to be called after calling prepareWriting
* @param data a pointer to the data
* @param size the size of the chunk
* @return @c true if successful, @c false otherwise
*/
bool writeData(const char *data, qint64 size);
/**
* Overload for writeData(const char *, qint64);
* @since 6.0
*/
bool writeData(QByteArrayView data);
/**
* Call finishWriting after writing the data.
* @param size the size of the file
* @see prepareWriting()
*/
bool finishWriting(qint64 size);
protected:
/**
* Opens an archive for reading or writing.
* Called by open.
* @param mode may be QIODevice::ReadOnly or QIODevice::WriteOnly
*/
virtual bool openArchive(QIODevice::OpenMode mode) = 0;
/**
* Closes the archive.
* Called by close.
*/
virtual bool closeArchive() = 0;
/**
* Sets error description
* @param errorStr error description
* @since 5.29
*/
void setErrorString(const QString &errorStr);
/**
* Retrieves or create the root directory.
* The default implementation assumes that openArchive() did the parsing,
* so it creates a dummy rootdir if none was set (write mode, or no '/' in the archive).
* Reimplement this to provide parsing/listing on demand.
* @return the root directory
*/
virtual KArchiveDirectory *rootDir();
/**
* Write a directory to the archive.
* This virtual method must be implemented by subclasses.
*
* Depending on the archive type not all metadata might be used.
*
* @param name the name of the directory
* @param user the user that owns the directory
* @param group the group that owns the directory
* @param perm permissions of the directory. Use 040755 if you don't have any other information.
* @param atime time the file was last accessed
* @param mtime modification time of the file
* @param ctime time of last status change
* @see writeDir
*/
virtual bool doWriteDir(const QString &name,
const QString &user,
const QString &group,
mode_t perm,
const QDateTime &atime,
const QDateTime &mtime,
const QDateTime &ctime) = 0;
/**
* Writes a symbolic link to the archive.
* This virtual method must be implemented by subclasses.
*
* @param name name of symbolic link
* @param target target of symbolic link
* @param user the user that owns the directory
* @param group the group that owns the directory
* @param perm permissions of the directory
* @param atime time the file was last accessed
* @param mtime modification time of the file
* @param ctime time of last status change
* @see writeSymLink
*/
virtual bool doWriteSymLink(const QString &name,
const QString &target,
const QString &user,
const QString &group,
mode_t perm,
const QDateTime &atime,
const QDateTime &mtime,
const QDateTime &ctime) = 0;
/**
* This virtual method must be implemented by subclasses.
*
* Depending on the archive type not all metadata might be used.
*
* @param name the name of the file
* @param user the user that owns the file
* @param group the group that owns the file
* @param size the size of the file
* @param perm permissions of the file. Use 0100644 if you don't have any more specific permissions to set.
* @param atime time the file was last accessed
* @param mtime modification time of the file
* @param ctime time of last status change
* @see prepareWriting
*/
virtual bool doPrepareWriting(const QString &name,
const QString &user,
const QString &group,
qint64 size,
mode_t perm,
const QDateTime &atime,
const QDateTime &mtime,
const QDateTime &ctime) = 0;
/**
* Write data into the current file.
* Called by writeData.
*
* @param data a pointer to the data
* @param size the size of the chunk
* @return @c true if successful, @c false otherwise
* @see writeData
* @since 6.0
*/
virtual bool doWriteData(const char *data, qint64 size);
/**
* Called after writing the data.
* This virtual method must be implemented by subclasses.
*
* @param size the size of the file
* @see finishWriting()
*/
virtual bool doFinishWriting(qint64 size) = 0;
/**
* Ensures that @p path exists, create otherwise.
* This handles e.g. tar files missing directory entries, like mico-2.3.0.tar.gz :)
* @param path the path of the directory
* @return the directory with the given @p path
*/
KArchiveDirectory *findOrCreate(const QString &path);
/**
* Can be reimplemented in order to change the creation of the device
* (when using the fileName constructor). By default this method uses
* QSaveFile when saving, and a simple QFile on reading.
* This method is called by open().
*/
virtual bool createDevice(QIODevice::OpenMode mode);
/**
* Can be called by derived classes in order to set the underlying device.
* Note that KArchive will -not- own the device, it must be deleted by the derived class.
*/
void setDevice(QIODevice *dev);
/**
* Derived classes call setRootDir from openArchive,
* to set the root directory after parsing an existing archive.
*/
void setRootDir(KArchiveDirectory *rootDir);
protected:
virtual void virtual_hook(int id, void *data);
private:
friend class KArchivePrivate;
KArchivePrivate *const d;
};
// for source compat
#include "karchivedirectory.h"
#include "karchivefile.h"
#endif
@@ -0,0 +1,59 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000-2005 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2003 Leo Savernik <l.savernik@aon.at>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KARCHIVE_P_H
#define KARCHIVE_P_H
#include "karchive.h"
#include <QSaveFile>
class KArchivePrivate
{
Q_DECLARE_TR_FUNCTIONS(KArchivePrivate)
public:
KArchivePrivate(KArchive *parent)
: q(parent)
{
}
~KArchivePrivate()
{
if (deviceOwned) {
delete dev; // we created it ourselves in open()
dev = nullptr;
}
delete saveFile;
delete rootDir;
}
KArchivePrivate(const KArchivePrivate &) = delete;
KArchivePrivate &operator=(const KArchivePrivate &) = delete;
static bool hasRootDir(KArchive *archive)
{
return archive->d->rootDir;
}
void abortWriting();
static QDateTime time_tToDateTime(uint time_t);
KArchiveDirectory *findOrCreate(const QString &path, int recursionCounter);
KArchive *q = nullptr;
KArchiveDirectory *rootDir = nullptr;
QSaveFile *saveFile = nullptr;
QIODevice *dev = nullptr;
QString fileName;
QIODevice::OpenMode mode = QIODevice::NotOpen;
bool deviceOwned = false; // if true, we (KArchive) own dev and must delete it
QString errorStr{tr("Unknown error")};
};
#endif // KARCHIVE_P_H
@@ -0,0 +1,127 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000-2005 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2003 Leo Savernik <l.savernik@aon.at>
Moved from ktar.h by Roberto Teixeira <maragato@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KARCHIVEDIRECTORY_H
#define KARCHIVEDIRECTORY_H
#include <sys/stat.h>
#include <sys/types.h>
#include <QDate>
#include <QString>
#include <QStringList>
#include <karchiveentry.h>
class KArchiveDirectoryPrivate;
class KArchiveFile;
/**
* @class KArchiveDirectory karchivedirectory.h KArchiveDirectory
*
* Represents a directory entry in a KArchive.
* @short A directory in an archive.
*
* @see KArchive
* @see KArchiveFile
*/
class KARCHIVE_EXPORT KArchiveDirectory : public KArchiveEntry
{
public:
/**
* Creates a new directory entry.
* @param archive the entries archive
* @param name the name of the entry
* @param access the permissions in unix format
* @param date the date (in seconds since 1970)
* @param user the user that owns the entry
* @param group the group that owns the entry
* @param symlink the symlink, or QString()
*/
KArchiveDirectory(KArchive *archive,
const QString &name,
int access,
const QDateTime &date,
const QString &user,
const QString &group,
const QString &symlink);
~KArchiveDirectory() override;
/**
* Returns a list of sub-entries.
* Note that the list is not sorted, it's even in random order (due to using a hashtable).
* Use sort() on the result to sort the list by filename.
*
* @return the names of all entries in this directory (filenames, no path).
*/
QStringList entries() const;
/**
* Returns the entry in the archive with the given name.
* The entry could be a file or a directory, use isFile() to find out which one it is.
* @param name may be "test1", "mydir/test3", "mydir/mysubdir/test3", etc.
* @return a pointer to the entry in the directory, or a null pointer if there is no such entry.
*/
const KArchiveEntry *entry(const QString &name) const;
/**
* Returns the file entry in the archive with the given name.
* If the entry exists and is a file, a KArchiveFile is returned.
* Otherwise, a null pointer is returned.
* This is a convenience method for entry(), when we know the entry is expected to be a file.
*
* @param name may be "test1", "mydir/test3", "mydir/mysubdir/test3", etc.
* @return a pointer to the file entry in the directory, or a null pointer if there is no such file entry.
* @since 5.3
*/
const KArchiveFile *file(const QString &name) const;
/**
* @internal
* Adds a new entry to the directory.
* Note: this can delete the entry if another one with the same name is already present
*/
void addEntry(KArchiveEntry *); // KF6 TODO: return bool
/**
* @internal
* Adds a new entry to the directory.
* @return whether the entry was added or not. Non added entries are deleted
*/
bool addEntryV2(KArchiveEntry *); // KF6 TODO: merge with the one above
/**
* @internal
* Removes an entry from the directory.
*/
void removeEntry(KArchiveEntry *); // KF6 TODO: return bool since it can fail
/**
* Checks whether this entry is a directory.
* @return true, since this entry is a directory
*/
bool isDirectory() const override;
/**
* Extracts all entries in this archive directory to the directory
* @p dest.
* @param dest the directory to extract to
* @param recursive if set to true, subdirectories are extracted as well
* @return true on success, false if the directory (dest + '/' + name()) couldn't be created
*/
bool copyTo(const QString &dest, bool recursive = true) const;
protected:
void virtual_hook(int id, void *data) override;
private:
friend class KArchiveDirectoryPrivate;
KArchiveDirectoryPrivate *const d;
};
#endif
@@ -0,0 +1,111 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000-2005 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2003 Leo Savernik <l.savernik@aon.at>
Moved from ktar.h by Roberto Teixeira <maragato@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KARCHIVEENTRY_H
#define KARCHIVEENTRY_H
#include <sys/stat.h>
#include <sys/types.h>
#include <karchive_export.h>
#include <QDateTime>
#include <QString>
#ifdef Q_OS_WIN
#include <qplatformdefs.h> // mode_t
#endif
class KArchiveDirectory;
class KArchiveFile;
class KArchive;
class KArchiveEntryPrivate;
/**
* @class KArchiveEntry karchiveentry.h KArchiveEntry
*
* A base class for entries in an KArchive.
* @short Base class for the archive-file's directory structure.
*
* @see KArchiveFile
* @see KArchiveDirectory
*/
class KARCHIVE_EXPORT KArchiveEntry
{
public:
/**
* Creates a new entry.
* @param archive the entries archive
* @param name the name of the entry
* @param access the permissions in unix format
* @param date the date (in seconds since 1970)
* @param user the user that owns the entry
* @param group the group that owns the entry
* @param symlink the symlink, or QString()
*/
KArchiveEntry(KArchive *archive, const QString &name, int access, const QDateTime &date, const QString &user, const QString &group, const QString &symlink);
virtual ~KArchiveEntry();
/**
* Creation date of the file.
* @return the creation date
*/
QDateTime date() const;
/**
* Name of the file without path.
* @return the file name without path
*/
QString name() const;
/**
* The permissions and mode flags as returned by the stat() function
* in st_mode.
* @return the permissions
*/
mode_t permissions() const;
/**
* User who created the file.
* @return the owner of the file
*/
QString user() const;
/**
* Group of the user who created the file.
* @return the group of the file
*/
QString group() const;
/**
* Symlink if there is one.
* @return the symlink, or QString()
*/
QString symLinkTarget() const;
/**
* Checks whether the entry is a file.
* @return true if this entry is a file
*/
virtual bool isFile() const;
/**
* Checks whether the entry is a directory.
* @return true if this entry is a directory
*/
virtual bool isDirectory() const;
protected:
KArchive *archive() const;
protected:
virtual void virtual_hook(int id, void *data);
private:
KArchiveEntryPrivate *const d;
};
#endif
@@ -0,0 +1,109 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000-2005 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2003 Leo Savernik <l.savernik@aon.at>
Moved from ktar.h by Roberto Teixeira <maragato@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KARCHIVEFILE_H
#define KARCHIVEFILE_H
#include <karchiveentry.h>
class KArchiveFilePrivate;
/**
* @class KArchiveFile karchivefile.h KArchiveFile
*
* Represents a file entry in a KArchive.
* @short A file in an archive.
*
* @see KArchive
* @see KArchiveDirectory
*/
class KARCHIVE_EXPORT KArchiveFile : public KArchiveEntry
{
public:
/**
* Creates a new file entry. Do not call this, KArchive takes care of it.
* @param archive the entries archive
* @param name the name of the entry
* @param access the permissions in unix format
* @param date the date (in seconds since 1970)
* @param user the user that owns the entry
* @param group the group that owns the entry
* @param symlink the symlink, or QString()
* @param pos the position of the file in the directory
* @param size the size of the file
*/
KArchiveFile(KArchive *archive,
const QString &name,
int access,
const QDateTime &date,
const QString &user,
const QString &group,
const QString &symlink,
qint64 pos,
qint64 size);
/**
* Destructor. Do not call this, KArchive takes care of it.
*/
~KArchiveFile() override;
/**
* Position of the data in the [uncompressed] archive.
* @return the position of the file
*/
qint64 position() const;
/**
* Size of the data.
* @return the size of the file
*/
qint64 size() const;
/**
* Set size of data, usually after writing the file.
* @param s the new size of the file
*/
void setSize(qint64 s);
/**
* Returns the data of the file.
* Call data() with care (only once per file), this data isn't cached.
* @return the content of this file.
*/
virtual QByteArray data() const;
/**
* This method returns QIODevice (internal class: KLimitedIODevice)
* on top of the underlying QIODevice. This is obviously for reading only.
*
* WARNING: Note that the ownership of the device is being transferred to the caller,
* who will have to delete it.
*
* The returned device auto-opens (in readonly mode), no need to open it.
* @return the QIODevice of the file
*/
virtual QIODevice *createDevice() const;
/**
* Checks whether this entry is a file.
* @return true, since this entry is a file
*/
bool isFile() const override;
/**
* Extracts the file to the directory @p dest
* @param dest the directory to extract to
* @return true on success, false if the file (dest + '/' + name()) couldn't be created
*/
bool copyTo(const QString &dest) const;
protected:
void virtual_hook(int id, void *data) override;
private:
KArchiveFilePrivate *const d;
};
#endif
@@ -0,0 +1,198 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000-2005 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kbzip2filter.h"
#include "loggingcategory.h"
#if HAVE_BZIP2_SUPPORT
// we don't need that
#define BZ_NO_STDIO
extern "C" {
#include <bzlib.h>
}
#if NEED_BZ2_PREFIX
#define bzDecompressInit(x, y, z) BZ2_bzDecompressInit(x, y, z)
#define bzDecompressEnd(x) BZ2_bzDecompressEnd(x)
#define bzCompressEnd(x) BZ2_bzCompressEnd(x)
#define bzDecompress(x) BZ2_bzDecompress(x)
#define bzCompress(x, y) BZ2_bzCompress(x, y)
#define bzCompressInit(x, y, z, a) BZ2_bzCompressInit(x, y, z, a);
#endif
#include <QDebug>
#include <QIODevice>
// For docu on this, see /usr/doc/bzip2-0.9.5d/bzip2-0.9.5d/manual_3.html
class Q_DECL_HIDDEN KBzip2Filter::Private
{
public:
Private()
: isInitialized(false)
{
memset(&zStream, 0, sizeof(zStream));
mode = 0;
}
bz_stream zStream;
int mode;
bool isInitialized;
};
KBzip2Filter::KBzip2Filter()
: d(new Private)
{
}
KBzip2Filter::~KBzip2Filter()
{
delete d;
}
bool KBzip2Filter::init(int mode)
{
if (d->isInitialized) {
terminate();
}
d->zStream.next_in = nullptr;
d->zStream.avail_in = 0;
if (mode == QIODevice::ReadOnly) {
const int result = bzDecompressInit(&d->zStream, 0, 0);
if (result != BZ_OK) {
// qCDebug(KArchiveLog) << "bzDecompressInit returned " << result;
return false;
}
} else if (mode == QIODevice::WriteOnly) {
const int result = bzCompressInit(&d->zStream, 5, 0, 0);
if (result != BZ_OK) {
// qCDebug(KArchiveLog) << "bzDecompressInit returned " << result;
return false;
}
} else {
// qCWarning(KArchiveLog) << "Unsupported mode " << mode << ". Only QIODevice::ReadOnly and QIODevice::WriteOnly supported";
return false;
}
d->mode = mode;
d->isInitialized = true;
return true;
}
int KBzip2Filter::mode() const
{
return d->mode;
}
bool KBzip2Filter::terminate()
{
if (d->mode == QIODevice::ReadOnly) {
const int result = bzDecompressEnd(&d->zStream);
if (result != BZ_OK) {
// qCDebug(KArchiveLog) << "bzDecompressEnd returned " << result;
return false;
}
} else if (d->mode == QIODevice::WriteOnly) {
const int result = bzCompressEnd(&d->zStream);
if (result != BZ_OK) {
// qCDebug(KArchiveLog) << "bzCompressEnd returned " << result;
return false;
}
} else {
// qCWarning(KArchiveLog) << "Unsupported mode " << d->mode << ". Only QIODevice::ReadOnly and QIODevice::WriteOnly supported";
return false;
}
d->isInitialized = false;
return true;
}
void KBzip2Filter::reset()
{
// bzip2 doesn't seem to have a reset call...
terminate();
init(d->mode);
}
void KBzip2Filter::setOutBuffer(char *data, uint maxlen)
{
d->zStream.avail_out = maxlen;
d->zStream.next_out = data;
}
void KBzip2Filter::setInBuffer(const char *data, unsigned int size)
{
d->zStream.avail_in = size;
d->zStream.next_in = const_cast<char *>(data);
}
int KBzip2Filter::inBufferAvailable() const
{
return d->zStream.avail_in;
}
int KBzip2Filter::outBufferAvailable() const
{
return d->zStream.avail_out;
}
KBzip2Filter::Result KBzip2Filter::uncompress()
{
// qCDebug(KArchiveLog) << "Calling bzDecompress with avail_in=" << inBufferAvailable() << " avail_out=" << outBufferAvailable();
int result = bzDecompress(&d->zStream);
if (result < BZ_OK) {
bzDecompressEnd(&d->zStream);
}
switch (result) {
case BZ_OK:
return KFilterBase::Ok;
case BZ_STREAM_END:
return KFilterBase::End;
case BZ_MEM_ERROR:
qCWarning(KArchiveLog) << "bzDecompress error, insufficient memory";
break;
case BZ_DATA_ERROR:
qCWarning(KArchiveLog) << "bzDecompress error, data integrity error";
break;
case BZ_DATA_ERROR_MAGIC:
qCWarning(KArchiveLog) << "bzDecompress error, stream does not start with the right magic bytes";
break;
case BZ_PARAM_ERROR:
qCWarning(KArchiveLog) << "bzDecompress error, parameter error";
break;
default:
qCWarning(KArchiveLog) << "bzDecompress error, returned:" << result;
break;
}
return KFilterBase::Error;
}
KBzip2Filter::Result KBzip2Filter::compress(bool finish)
{
// qCDebug(KArchiveLog) << "Calling bzCompress with avail_in=" << inBufferAvailable() << " avail_out=" << outBufferAvailable();
int result = bzCompress(&d->zStream, finish ? BZ_FINISH : BZ_RUN);
switch (result) {
case BZ_OK:
case BZ_FLUSH_OK:
case BZ_RUN_OK:
case BZ_FINISH_OK:
return KFilterBase::Ok;
break;
case BZ_STREAM_END:
// qCDebug(KArchiveLog) << " bzCompress returned " << result;
return KFilterBase::End;
break;
default:
// qCDebug(KArchiveLog) << " bzCompress returned " << result;
return KFilterBase::Error;
break;
}
}
#endif /* HAVE_BZIP2_SUPPORT */
@@ -0,0 +1,52 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef __kbzip2filter__h
#define __kbzip2filter__h
#include <config-compression.h>
#if HAVE_BZIP2_SUPPORT
#include "kfilterbase.h"
/**
* Internal class used by KCompressionDevice
* @internal
*/
class KBzip2Filter : public KFilterBase
{
public:
KBzip2Filter();
~KBzip2Filter() override;
bool init(int) override;
int mode() const override;
bool terminate() override;
void reset() override;
bool readHeader() override
{
return true; // bzip2 handles it by itself ! Cool !
}
bool writeHeader(const QByteArray &) override
{
return true;
}
void setOutBuffer(char *data, uint maxlen) override;
void setInBuffer(const char *data, uint size) override;
int inBufferAvailable() const override;
int outBufferAvailable() const override;
Result uncompress() override;
Result compress(bool finish) override;
private:
class Private;
Private *const d;
};
#endif
#endif
@@ -0,0 +1,519 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2011 Mario Bensi <mbensi@ipsquad.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kcompressiondevice.h"
#include "kcompressiondevice_p.h"
#include "kfilterbase.h"
#include "loggingcategory.h"
#include "kgzipfilter.h"
#include "knonefilter.h"
#include "config-compression.h"
#if HAVE_BZIP2_SUPPORT
#include "kbzip2filter.h"
#endif
#if HAVE_XZ_SUPPORT
#include "kxzfilter.h"
#endif
#if HAVE_ZSTD_SUPPORT
#include "kzstdfilter.h"
#endif
#include <QDebug>
#include <QFile>
#include <QMimeDatabase>
#include <assert.h>
#include <stdio.h> // for EOF
#include <stdlib.h>
class KCompressionDevicePrivate
{
public:
KCompressionDevicePrivate(KCompressionDevice *qq)
: bNeedHeader(true)
, bSkipHeaders(false)
, bOpenedUnderlyingDevice(false)
, type(KCompressionDevice::None)
, errorCode(QFileDevice::NoError)
, deviceReadPos(0)
, q(qq)
{
}
void propagateErrorCode();
bool bNeedHeader;
bool bSkipHeaders;
bool bOpenedUnderlyingDevice;
QByteArray buffer; // Used as 'input buffer' when reading, as 'output buffer' when writing
QByteArray origFileName;
KFilterBase::Result result;
KFilterBase *filter;
KCompressionDevice::CompressionType type;
QFileDevice::FileError errorCode;
qint64 deviceReadPos;
KCompressionDevice *q;
};
void KCompressionDevicePrivate::propagateErrorCode()
{
QIODevice *dev = filter->device();
if (QFileDevice *fileDev = qobject_cast<QFileDevice *>(dev)) {
if (fileDev->error() != QFileDevice::NoError) {
errorCode = fileDev->error();
q->setErrorString(dev->errorString());
}
}
// ... we have no generic way to propagate errors from other kinds of iodevices. Sucks, heh? :(
}
static KCompressionDevice::CompressionType findCompressionByFileName(const QString &fileName)
{
if (fileName.endsWith(QLatin1String(".gz"), Qt::CaseInsensitive)) {
return KCompressionDevice::GZip;
}
#if HAVE_BZIP2_SUPPORT
if (fileName.endsWith(QLatin1String(".bz2"), Qt::CaseInsensitive)) {
return KCompressionDevice::BZip2;
}
#endif
#if HAVE_XZ_SUPPORT
if (fileName.endsWith(QLatin1String(".lzma"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String(".xz"), Qt::CaseInsensitive)) {
return KCompressionDevice::Xz;
}
#endif
#if HAVE_ZSTD_SUPPORT
if (fileName.endsWith(QLatin1String(".zst"), Qt::CaseInsensitive)) {
return KCompressionDevice::Zstd;
}
#endif
else {
// not a warning, since this is called often with other MIME types (see #88574)...
// maybe we can avoid that though?
// qCDebug(KArchiveLog) << "findCompressionByFileName : no compression found for " << fileName;
}
return KCompressionDevice::None;
}
KCompressionDevice::CompressionType KCompressionDevice::compressionTypeForMimeType(const QString &mimeType)
{
if (mimeType == QLatin1String("application/gzip") //
|| mimeType == QLatin1String("application/x-gzip") // legacy name, kept for compatibility
) {
return KCompressionDevice::GZip;
}
#if HAVE_BZIP2_SUPPORT
if (mimeType == QLatin1String("application/x-bzip") //
|| mimeType == QLatin1String("application/x-bzip2") // old name, kept for compatibility
) {
return KCompressionDevice::BZip2;
}
#endif
#if HAVE_XZ_SUPPORT
if (mimeType == QLatin1String("application/x-lzma") // legacy name, still used
|| mimeType == QLatin1String("application/x-xz") // current naming
) {
return KCompressionDevice::Xz;
}
#endif
#if HAVE_ZSTD_SUPPORT
if (mimeType == QLatin1String("application/zstd")) {
return KCompressionDevice::Zstd;
}
#endif
QMimeDatabase db;
const QMimeType mime = db.mimeTypeForName(mimeType);
if (mime.isValid()) {
// use legacy MIME type for now, see comment in impl. of KTar(const QString &, const QString &_mimetype)
if (mime.inherits(QStringLiteral("application/x-gzip"))) {
return KCompressionDevice::GZip;
}
#if HAVE_BZIP2_SUPPORT
if (mime.inherits(QStringLiteral("application/x-bzip"))) {
return KCompressionDevice::BZip2;
}
#endif
#if HAVE_XZ_SUPPORT
if (mime.inherits(QStringLiteral("application/x-lzma"))) {
return KCompressionDevice::Xz;
}
if (mime.inherits(QStringLiteral("application/x-xz"))) {
return KCompressionDevice::Xz;
}
#endif
}
// not a warning, since this is called often with other MIME types (see #88574)...
// maybe we can avoid that though?
// qCDebug(KArchiveLog) << "no compression found for" << mimeType;
return KCompressionDevice::None;
}
KFilterBase *KCompressionDevice::filterForCompressionType(KCompressionDevice::CompressionType type)
{
switch (type) {
case KCompressionDevice::GZip:
return new KGzipFilter;
case KCompressionDevice::BZip2:
#if HAVE_BZIP2_SUPPORT
return new KBzip2Filter;
#else
return nullptr;
#endif
case KCompressionDevice::Xz:
#if HAVE_XZ_SUPPORT
return new KXzFilter;
#else
return nullptr;
#endif
case KCompressionDevice::None:
return new KNoneFilter;
case KCompressionDevice::Zstd:
#if HAVE_ZSTD_SUPPORT
return new KZstdFilter;
#else
return nullptr;
#endif
}
return nullptr;
}
KCompressionDevice::KCompressionDevice(QIODevice *inputDevice, bool autoDeleteInputDevice, CompressionType type)
: d(new KCompressionDevicePrivate(this))
{
assert(inputDevice);
d->filter = filterForCompressionType(type);
if (d->filter) {
d->type = type;
d->filter->setDevice(inputDevice, autoDeleteInputDevice);
}
}
KCompressionDevice::KCompressionDevice(const QString &fileName, CompressionType type)
: d(new KCompressionDevicePrivate(this))
{
QFile *f = new QFile(fileName);
d->filter = filterForCompressionType(type);
if (d->filter) {
d->type = type;
d->filter->setDevice(f, true);
} else {
delete f;
}
}
KCompressionDevice::KCompressionDevice(const QString &fileName)
: KCompressionDevice(fileName, findCompressionByFileName(fileName))
{
}
KCompressionDevice::~KCompressionDevice()
{
if (isOpen()) {
close();
}
delete d->filter;
delete d;
}
KCompressionDevice::CompressionType KCompressionDevice::compressionType() const
{
return d->type;
}
bool KCompressionDevice::open(QIODevice::OpenMode mode)
{
if (isOpen()) {
// qCWarning(KArchiveLog) << "KCompressionDevice::open: device is already open";
return true; // QFile returns false, but well, the device -is- open...
}
if (!d->filter) {
return false;
}
d->bOpenedUnderlyingDevice = false;
// qCDebug(KArchiveLog) << mode;
if (mode == QIODevice::ReadOnly) {
d->buffer.resize(0);
} else {
d->buffer.resize(BUFFER_SIZE);
d->filter->setOutBuffer(d->buffer.data(), d->buffer.size());
}
if (!d->filter->device()->isOpen()) {
if (!d->filter->device()->open(mode)) {
// qCWarning(KArchiveLog) << "KCompressionDevice::open: Couldn't open underlying device";
d->propagateErrorCode();
return false;
}
d->bOpenedUnderlyingDevice = true;
}
d->bNeedHeader = !d->bSkipHeaders;
d->filter->setFilterFlags(d->bSkipHeaders ? KFilterBase::NoHeaders : KFilterBase::WithHeaders);
if (!d->filter->init(mode & ~QIODevice::Truncate)) {
return false;
}
d->result = KFilterBase::Ok;
setOpenMode(mode);
return true;
}
void KCompressionDevice::close()
{
if (!isOpen()) {
return;
}
if (d->filter->mode() == QIODevice::WriteOnly && d->errorCode == QFileDevice::NoError) {
write(nullptr, 0); // finish writing
}
// qCDebug(KArchiveLog) << "Calling terminate().";
if (!d->filter->terminate()) {
// qCWarning(KArchiveLog) << "KCompressionDevice::close: terminate returned an error";
d->errorCode = QFileDevice::UnspecifiedError;
}
if (d->bOpenedUnderlyingDevice) {
QIODevice *dev = d->filter->device();
dev->close();
d->propagateErrorCode();
}
setOpenMode(QIODevice::NotOpen);
}
QFileDevice::FileError KCompressionDevice::error() const
{
return d->errorCode;
}
bool KCompressionDevice::seek(qint64 pos)
{
if (d->deviceReadPos == pos) {
return QIODevice::seek(pos);
}
// qCDebug(KArchiveLog) << "seek(" << pos << ") called, current pos=" << QIODevice::pos();
Q_ASSERT(d->filter->mode() == QIODevice::ReadOnly);
if (pos == 0) {
if (!QIODevice::seek(pos)) {
return false;
}
// We can forget about the cached data
d->bNeedHeader = !d->bSkipHeaders;
d->result = KFilterBase::Ok;
d->filter->setInBuffer(nullptr, 0);
d->filter->reset();
d->deviceReadPos = 0;
return d->filter->device()->reset();
}
qint64 bytesToRead;
if (d->deviceReadPos < pos) { // we can start from here
bytesToRead = pos - d->deviceReadPos;
// Since we're going to do a read() below
// we need to reset the internal QIODevice pos to the real position we are
// so that after read() we are indeed pointing to the pos seek
// asked us to be in
if (!QIODevice::seek(d->deviceReadPos)) {
return false;
}
} else {
// we have to start from 0 ! Ugly and slow, but better than the previous
// solution (KTarGz was allocating everything into memory)
if (!seek(0)) { // recursive
return false;
}
bytesToRead = pos;
}
// qCDebug(KArchiveLog) << "reading " << bytesToRead << " dummy bytes";
QByteArray dummy(qMin(bytesToRead, qint64(SEEK_BUFFER_SIZE)), 0);
while (bytesToRead > 0) {
const qint64 bytesToReadThisTime = qMin(bytesToRead, qint64(dummy.size()));
const bool result = (read(dummy.data(), bytesToReadThisTime) == bytesToReadThisTime);
if (!result) {
return false;
}
bytesToRead -= bytesToReadThisTime;
}
return true;
}
bool KCompressionDevice::atEnd() const
{
return (d->type == KCompressionDevice::None || d->result == KFilterBase::End) //
&& QIODevice::atEnd() // take QIODevice's internal buffer into account
&& d->filter->device()->atEnd();
}
qint64 KCompressionDevice::readData(char *data, qint64 maxlen)
{
Q_ASSERT(d->filter->mode() == QIODevice::ReadOnly);
// qCDebug(KArchiveLog) << "maxlen=" << maxlen;
KFilterBase *filter = d->filter;
uint dataReceived = 0;
// We came to the end of the stream
if (d->result == KFilterBase::End) {
return dataReceived;
}
// If we had an error, return -1.
if (d->result != KFilterBase::Ok) {
return -1;
}
qint64 availOut = maxlen;
filter->setOutBuffer(data, maxlen);
while (dataReceived < maxlen) {
if (filter->inBufferEmpty()) {
// Not sure about the best size to set there.
// For sure, it should be bigger than the header size (see comment in readHeader)
d->buffer.resize(BUFFER_SIZE);
// Request data from underlying device
int size = filter->device()->read(d->buffer.data(), d->buffer.size());
// qCDebug(KArchiveLog) << "got" << size << "bytes from device";
if (size) {
filter->setInBuffer(d->buffer.data(), size);
} else {
// Not enough data available in underlying device for now
break;
}
}
if (d->bNeedHeader) {
(void)filter->readHeader();
d->bNeedHeader = false;
}
d->result = filter->uncompress();
if (d->result == KFilterBase::Error) {
// qCWarning(KArchiveLog) << "KCompressionDevice: Error when uncompressing data";
break;
}
// We got that much data since the last time we went here
uint outReceived = availOut - filter->outBufferAvailable();
// qCDebug(KArchiveLog) << "avail_out = " << filter->outBufferAvailable() << " result=" << d->result << " outReceived=" << outReceived;
if (availOut < uint(filter->outBufferAvailable())) {
// qCWarning(KArchiveLog) << " last availOut " << availOut << " smaller than new avail_out=" << filter->outBufferAvailable() << " !";
}
dataReceived += outReceived;
data += outReceived;
availOut = maxlen - dataReceived;
if (d->result == KFilterBase::End) {
// We're actually at the end, no more data to check
if (filter->device()->atEnd()) {
break;
}
// Still not done, re-init and try again
filter->init(filter->mode());
}
filter->setOutBuffer(data, availOut);
}
d->deviceReadPos += dataReceived;
return dataReceived;
}
qint64 KCompressionDevice::writeData(const char *data /*0 to finish*/, qint64 len)
{
KFilterBase *filter = d->filter;
Q_ASSERT(filter->mode() == QIODevice::WriteOnly);
// If we had an error, return 0.
if (d->result != KFilterBase::Ok) {
return 0;
}
bool finish = (data == nullptr);
if (!finish) {
filter->setInBuffer(data, len);
if (d->bNeedHeader) {
(void)filter->writeHeader(d->origFileName);
d->bNeedHeader = false;
}
}
uint dataWritten = 0;
uint availIn = len;
while (dataWritten < len || finish) {
d->result = filter->compress(finish);
if (d->result == KFilterBase::Error) {
// qCWarning(KArchiveLog) << "KCompressionDevice: Error when compressing data";
// What to do ?
break;
}
// Wrote everything ?
if (filter->inBufferEmpty() || (d->result == KFilterBase::End)) {
// We got that much data since the last time we went here
uint wrote = availIn - filter->inBufferAvailable();
// qCDebug(KArchiveLog) << " Wrote everything for now. avail_in=" << filter->inBufferAvailable() << "result=" << d->result << "wrote=" << wrote;
// Move on in the input buffer
data += wrote;
dataWritten += wrote;
availIn = len - dataWritten;
// qCDebug(KArchiveLog) << " availIn=" << availIn << "dataWritten=" << dataWritten << "pos=" << pos();
if (availIn > 0) {
filter->setInBuffer(data, availIn);
}
}
if (filter->outBufferFull() || (d->result == KFilterBase::End) || finish) {
// qCDebug(KArchiveLog) << " writing to underlying. avail_out=" << filter->outBufferAvailable();
int towrite = d->buffer.size() - filter->outBufferAvailable();
if (towrite > 0) {
// Write compressed data to underlying device
int size = filter->device()->write(d->buffer.data(), towrite);
if (size != towrite) {
// qCWarning(KArchiveLog) << "KCompressionDevice::write. Could only write " << size << " out of " << towrite << " bytes";
d->errorCode = QFileDevice::WriteError;
setErrorString(tr("Could not write. Partition full?"));
return 0; // indicate an error
}
// qCDebug(KArchiveLog) << " wrote " << size << " bytes";
}
if (d->result == KFilterBase::End) {
Q_ASSERT(finish); // hopefully we don't get end before finishing
break;
}
d->buffer.resize(BUFFER_SIZE);
filter->setOutBuffer(d->buffer.data(), d->buffer.size());
}
}
return dataWritten;
}
void KCompressionDevice::setOrigFileName(const QByteArray &fileName)
{
d->origFileName = fileName;
}
void KCompressionDevice::setSkipHeaders()
{
d->bSkipHeaders = true;
}
KFilterBase *KCompressionDevice::filterBase()
{
return d->filter;
}
#include "moc_kcompressiondevice.cpp"
@@ -0,0 +1,146 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2011 Mario Bensi <mbensi@ipsquad.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef __kcompressiondevice_h
#define __kcompressiondevice_h
#include <karchive_export.h>
#include <QFileDevice>
#include <QIODevice>
#include <QMetaType>
#include <QString>
class KCompressionDevicePrivate;
class KFilterBase;
/**
* @class KCompressionDevice kcompressiondevice.h KCompressionDevice
*
* A class for reading and writing compressed data onto a device
* (e.g. file, but other usages are possible, like a buffer or a socket).
*
* Use this class to read/write compressed files.
*/
class KARCHIVE_EXPORT KCompressionDevice : public QIODevice // KF6 TODO: consider inheriting from QFileDevice, so apps can use error() generically ?
{
Q_OBJECT
public:
enum CompressionType {
GZip,
BZip2,
Xz,
None,
Zstd, ///< @since 5.82
};
/**
* Constructs a KCompressionDevice for a given CompressionType (e.g. GZip, BZip2 etc.).
* @param inputDevice input device.
* @param autoDeleteInputDevice if true, @p inputDevice will be deleted automatically
* @param type the CompressionType to use.
*/
KCompressionDevice(QIODevice *inputDevice, bool autoDeleteInputDevice, CompressionType type);
/**
* Constructs a KCompressionDevice for a given CompressionType (e.g. GZip, BZip2 etc.).
* @param fileName the name of the file to filter.
* @param type the CompressionType to use.
*/
KCompressionDevice(const QString &fileName, CompressionType type);
/**
* Constructs a KCompressionDevice for a given @p fileName.
* @param fileName the name of the file to filter.
* @since 5.85
*/
explicit KCompressionDevice(const QString &fileName);
/**
* Destructs the KCompressionDevice.
* Calls close() if the filter device is still open.
*/
~KCompressionDevice() override;
/**
* The compression actually used by this device.
* If the support for the compression requested in the constructor
* is not available, then the device will use None.
*/
CompressionType compressionType() const;
/**
* Open for reading or writing.
*/
bool open(QIODevice::OpenMode mode) override;
/**
* Close after reading or writing.
*/
void close() override;
/**
* For writing gzip compressed files only:
* set the name of the original file, to be used in the gzip header.
* @param fileName the name of the original file
*/
void setOrigFileName(const QByteArray &fileName);
/**
* Call this let this device skip the gzip headers when reading/writing.
* This way KCompressionDevice (with gzip filter) can be used as a direct wrapper
* around zlib - this is used by KZip.
*/
void setSkipHeaders();
/**
* That one can be quite slow, when going back. Use with care.
*/
bool seek(qint64) override;
bool atEnd() const override;
/**
* Call this to create the appropriate filter for the CompressionType
* named @p type.
* @param type the type of the compression filter
* @return the filter for the @p type, or 0 if not found
*/
static KFilterBase *filterForCompressionType(CompressionType type);
/**
* Returns the compression type for the given MIME type, if possible. Otherwise returns None.
* This handles simple cases like application/gzip, but also application/x-compressed-tar, and inheritance.
* @since 5.85
*/
static CompressionType compressionTypeForMimeType(const QString &mimetype);
/**
* Returns the error code from the last failing operation.
* This is especially useful after calling close(), which unfortunately returns void
* (see https://bugreports.qt.io/browse/QTBUG-70033), to see if the flushing done by close
* was able to write all the data to disk.
*/
QFileDevice::FileError error() const;
protected:
friend class K7Zip;
qint64 readData(char *data, qint64 maxlen) override;
qint64 writeData(const char *data, qint64 len) override;
KFilterBase *filterBase();
private:
friend KCompressionDevicePrivate;
KCompressionDevicePrivate *const d;
};
Q_DECLARE_METATYPE(KCompressionDevice::CompressionType)
#endif
@@ -0,0 +1,13 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2011 Mario Bensi <mbensi@ipsquad.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef __kcompressiondevice_p_h
#define __kcompressiondevice_p_h
#define BUFFER_SIZE 8 * 1024
#define SEEK_BUFFER_SIZE 3 * BUFFER_SIZE
#endif
@@ -0,0 +1,81 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000-2005 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kfilterbase.h"
#include <QIODevice>
class KFilterBasePrivate
{
public:
KFilterBasePrivate()
: m_flags(KFilterBase::WithHeaders)
, m_dev(nullptr)
, m_bAutoDel(false)
{
}
KFilterBase::FilterFlags m_flags;
QIODevice *m_dev;
bool m_bAutoDel;
};
KFilterBase::KFilterBase()
: d(new KFilterBasePrivate)
{
}
KFilterBase::~KFilterBase()
{
if (d->m_bAutoDel) {
delete d->m_dev;
}
delete d;
}
void KFilterBase::setDevice(QIODevice *dev, bool autodelete)
{
d->m_dev = dev;
d->m_bAutoDel = autodelete;
}
QIODevice *KFilterBase::device()
{
return d->m_dev;
}
bool KFilterBase::inBufferEmpty() const
{
return inBufferAvailable() == 0;
}
bool KFilterBase::outBufferFull() const
{
return outBufferAvailable() == 0;
}
bool KFilterBase::terminate()
{
return true;
}
void KFilterBase::reset()
{
}
void KFilterBase::setFilterFlags(FilterFlags flags)
{
d->m_flags = flags;
}
KFilterBase::FilterFlags KFilterBase::filterFlags() const
{
return d->m_flags;
}
void KFilterBase::virtual_hook(int, void *)
{
/*BASE::virtual_hook( id, data );*/
}
@@ -0,0 +1,109 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef __kfilterbase__h
#define __kfilterbase__h
#include <karchive_export.h>
#include <QObject>
#include <QString>
class KFilterBasePrivate;
class QIODevice;
/**
* @class KFilterBase kfilterbase.h KFilterBase
*
* This is the base class for compression filters
* such as gzip and bzip2. It's pretty much internal.
* Don't use directly, use KCompressionDevice instead.
* @internal
*/
class KARCHIVE_EXPORT KFilterBase
{
public:
KFilterBase();
virtual ~KFilterBase();
/**
* Sets the device on which the filter will work
* @param dev the device on which the filter will work
* @param autodelete if true, @p dev is deleted when the filter is deleted
*/
void setDevice(QIODevice *dev, bool autodelete = false);
// Note that this isn't in the constructor, because of KLibFactory::create,
// but it should be called before using the filterbase !
/**
* Returns the device on which the filter will work.
* @returns the device on which the filter will work
*/
QIODevice *device();
/** \internal */
virtual bool init(int mode) = 0;
/** \internal */
virtual int mode() const = 0;
/** \internal */
virtual bool terminate();
/** \internal */
virtual void reset();
/** \internal */
virtual bool readHeader() = 0;
/** \internal */
virtual bool writeHeader(const QByteArray &filename) = 0;
/** \internal */
virtual void setOutBuffer(char *data, uint maxlen) = 0;
/** \internal */
virtual void setInBuffer(const char *data, uint size) = 0;
/** \internal */
virtual bool inBufferEmpty() const;
/** \internal */
virtual int inBufferAvailable() const = 0;
/** \internal */
virtual bool outBufferFull() const;
/** \internal */
virtual int outBufferAvailable() const = 0;
/** \internal */
enum Result {
Ok,
End,
Error,
};
/** \internal */
virtual Result uncompress() = 0;
/** \internal */
virtual Result compress(bool finish) = 0;
/**
* \internal
* \since 4.3
*/
enum FilterFlags {
NoHeaders = 0,
WithHeaders = 1,
ZlibHeaders = 2, // only use for gzip compression
};
/**
* \internal
* \since 4.3
*/
void setFilterFlags(FilterFlags flags);
FilterFlags filterFlags() const;
protected:
/** Virtual hook, used to add new "virtual" functions while maintaining
binary compatibility. Unused in this class.
*/
virtual void virtual_hook(int id, void *data);
private:
Q_DISABLE_COPY(KFilterBase)
KFilterBasePrivate *const d;
};
#endif
@@ -0,0 +1,366 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000-2005 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kgzipfilter.h"
#include "loggingcategory.h"
#include <QDebug>
#include <QIODevice>
#include <time.h>
#include <zlib.h>
/* gzip flag byte */
#define ORIG_NAME 0x08 /* bit 3 set: original file name present */
// #define DEBUG_GZIP
class Q_DECL_HIDDEN KGzipFilter::Private
{
public:
Private()
: headerWritten(false)
, footerWritten(false)
, compressed(false)
, mode(0)
, crc(0)
, isInitialized(false)
{
zStream.zalloc = static_cast<alloc_func>(nullptr);
zStream.zfree = static_cast<free_func>(nullptr);
zStream.opaque = static_cast<voidpf>(nullptr);
}
z_stream zStream;
bool headerWritten;
bool footerWritten;
bool compressed;
int mode;
ulong crc;
bool isInitialized;
};
KGzipFilter::KGzipFilter()
: d(new Private)
{
}
KGzipFilter::~KGzipFilter()
{
delete d;
}
bool KGzipFilter::init(int mode)
{
switch (filterFlags()) {
case NoHeaders:
return init(mode, RawDeflate);
case WithHeaders:
return init(mode, GZipHeader);
case ZlibHeaders:
return init(mode, ZlibHeader);
}
return false;
}
bool KGzipFilter::init(int mode, Flag flag)
{
if (d->isInitialized) {
terminate();
}
d->zStream.next_in = Z_NULL;
d->zStream.avail_in = 0;
if (mode == QIODevice::ReadOnly) {
const int windowBits = (flag == RawDeflate) ? -MAX_WBITS /*no zlib header*/
: (flag == GZipHeader) ? MAX_WBITS + 32 /* auto-detect and eat gzip header */
: MAX_WBITS /*zlib header*/;
const int result = inflateInit2(&d->zStream, windowBits);
if (result != Z_OK) {
// qCDebug(KArchiveLog) << "inflateInit2 returned " << result;
return false;
}
} else if (mode == QIODevice::WriteOnly) {
int result = deflateInit2(&d->zStream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY); // same here
if (result != Z_OK) {
// qCDebug(KArchiveLog) << "deflateInit returned " << result;
return false;
}
} else {
// qCWarning(KArchiveLog) << "KGzipFilter: Unsupported mode " << mode << ". Only QIODevice::ReadOnly and QIODevice::WriteOnly supported";
return false;
}
d->mode = mode;
d->compressed = true;
d->headerWritten = false;
d->footerWritten = false;
d->isInitialized = true;
return true;
}
int KGzipFilter::mode() const
{
return d->mode;
}
bool KGzipFilter::terminate()
{
if (d->mode == QIODevice::ReadOnly) {
int result = inflateEnd(&d->zStream);
if (result != Z_OK) {
// qCDebug(KArchiveLog) << "inflateEnd returned " << result;
return false;
}
} else if (d->mode == QIODevice::WriteOnly) {
int result = deflateEnd(&d->zStream);
if (result != Z_OK) {
// qCDebug(KArchiveLog) << "deflateEnd returned " << result;
return false;
}
}
d->isInitialized = false;
return true;
}
void KGzipFilter::reset()
{
if (d->mode == QIODevice::ReadOnly) {
int result = inflateReset(&d->zStream);
if (result != Z_OK) {
// qCDebug(KArchiveLog) << "inflateReset returned " << result;
// TODO return false
}
} else if (d->mode == QIODevice::WriteOnly) {
int result = deflateReset(&d->zStream);
if (result != Z_OK) {
// qCDebug(KArchiveLog) << "deflateReset returned " << result;
// TODO return false
}
d->headerWritten = false;
d->footerWritten = false;
}
}
bool KGzipFilter::readHeader()
{
// We now rely on zlib to read the full header (see the MAX_WBITS + 32 in init).
// We just use this method to check if the data is actually compressed.
#ifdef DEBUG_GZIP
qCDebug(KArchiveLog) << "avail=" << d->zStream.avail_in;
#endif
// Assume not compressed until we see a gzip header
d->compressed = false;
const Bytef *p = d->zStream.next_in;
int i = d->zStream.avail_in;
if ((i -= 10) < 0) {
return false; // Need at least 10 bytes
}
#ifdef DEBUG_GZIP
qCDebug(KArchiveLog) << "first byte is " << QString::number(*p, 16);
#endif
if (*p++ != 0x1f) {
return false; // GZip magic
}
#ifdef DEBUG_GZIP
qCDebug(KArchiveLog) << "second byte is " << QString::number(*p, 16);
#endif
if (*p++ != 0x8b) {
return false;
}
d->compressed = true;
#ifdef DEBUG_GZIP
qCDebug(KArchiveLog) << "header OK";
#endif
return true;
}
/* Output a 16 bit value, lsb first */
#define put_short(w) \
*p++ = uchar((w)&0xff); \
*p++ = uchar(ushort(w) >> 8);
/* Output a 32 bit value to the bit stream, lsb first */
#define put_long(n) \
put_short((n)&0xffff); \
put_short((ulong(n)) >> 16);
bool KGzipFilter::writeHeader(const QByteArray &fileName)
{
Bytef *p = d->zStream.next_out;
int i = d->zStream.avail_out;
*p++ = 0x1f;
*p++ = 0x8b;
*p++ = Z_DEFLATED;
*p++ = ORIG_NAME;
put_long(time(nullptr)); // Modification time (in unix format)
*p++ = 0; // Extra flags (2=max compress, 4=fastest compress)
*p++ = 3; // Unix
uint len = fileName.length();
for (uint j = 0; j < len; ++j) {
*p++ = fileName[j];
}
*p++ = 0;
int headerSize = p - d->zStream.next_out;
i -= headerSize;
Q_ASSERT(i > 0);
d->crc = crc32(0L, nullptr, 0);
d->zStream.next_out = p;
d->zStream.avail_out = i;
d->headerWritten = true;
return true;
}
void KGzipFilter::writeFooter()
{
Q_ASSERT(d->headerWritten);
Q_ASSERT(!d->footerWritten);
Bytef *p = d->zStream.next_out;
int i = d->zStream.avail_out;
// qCDebug(KArchiveLog) << "avail_out=" << i << "writing CRC=" << QString::number(d->crc, 16) << "at p=" << p;
put_long(d->crc);
// qCDebug(KArchiveLog) << "writing totalin=" << d->zStream.total_in << "at p=" << p;
put_long(d->zStream.total_in);
i -= p - d->zStream.next_out;
d->zStream.next_out = p;
d->zStream.avail_out = i;
d->footerWritten = true;
}
void KGzipFilter::setOutBuffer(char *data, uint maxlen)
{
d->zStream.avail_out = maxlen;
d->zStream.next_out = reinterpret_cast<Bytef *>(data);
}
void KGzipFilter::setInBuffer(const char *data, uint size)
{
#ifdef DEBUG_GZIP
qCDebug(KArchiveLog) << "avail_in=" << size;
#endif
d->zStream.avail_in = size;
d->zStream.next_in = reinterpret_cast<Bytef *>(const_cast<char *>(data));
}
int KGzipFilter::inBufferAvailable() const
{
return d->zStream.avail_in;
}
int KGzipFilter::outBufferAvailable() const
{
return d->zStream.avail_out;
}
KGzipFilter::Result KGzipFilter::uncompress_noop()
{
// I'm not sure that we really need support for that (uncompressed streams),
// but why not, it can't hurt to have it. One case I can think of is someone
// naming a tar file "blah.tar.gz" :-)
if (d->zStream.avail_in > 0) {
int n = (d->zStream.avail_in < d->zStream.avail_out) ? d->zStream.avail_in : d->zStream.avail_out;
memcpy(d->zStream.next_out, d->zStream.next_in, n);
d->zStream.avail_out -= n;
d->zStream.next_in += n;
d->zStream.avail_in -= n;
return KFilterBase::Ok;
} else {
return KFilterBase::End;
}
}
KGzipFilter::Result KGzipFilter::uncompress()
{
#ifndef NDEBUG
if (d->mode == 0) {
// qCWarning(KArchiveLog) << "mode==0; KGzipFilter::init was not called!";
return KFilterBase::Error;
} else if (d->mode == QIODevice::WriteOnly) {
// qCWarning(KArchiveLog) << "uncompress called but the filter was opened for writing!";
return KFilterBase::Error;
}
Q_ASSERT(d->mode == QIODevice::ReadOnly);
#endif
if (!d->compressed) {
return uncompress_noop();
}
#ifdef DEBUG_GZIP
qCDebug(KArchiveLog) << "Calling inflate with avail_in=" << inBufferAvailable() << " avail_out=" << outBufferAvailable();
qCDebug(KArchiveLog) << " next_in=" << d->zStream.next_in;
#endif
while (d->zStream.avail_in > 0) {
int result = inflate(&d->zStream, Z_SYNC_FLUSH);
#ifdef DEBUG_GZIP
qCDebug(KArchiveLog) << " -> inflate returned " << result;
qCDebug(KArchiveLog) << " now avail_in=" << inBufferAvailable() << " avail_out=" << outBufferAvailable();
qCDebug(KArchiveLog) << " next_in=" << d->zStream.next_in;
#endif
if (result == Z_OK) {
return KFilterBase::Ok;
}
// We can't handle any other results
if (result != Z_STREAM_END) {
return KFilterBase::Error;
}
// It really was the end
if (d->zStream.avail_in == 0) {
return KFilterBase::End;
}
// Store before resetting
Bytef *data = d->zStream.next_in; // This is increased appropriately by zlib beforehand
uInt size = d->zStream.avail_in;
// Reset the stream, if that fails we assume we're at the end
if (!init(d->mode)) {
return KFilterBase::End;
}
// Reset the data to where we left off
d->zStream.next_in = data;
d->zStream.avail_in = size;
}
return KFilterBase::End;
}
KGzipFilter::Result KGzipFilter::compress(bool finish)
{
Q_ASSERT(d->compressed);
Q_ASSERT(d->mode == QIODevice::WriteOnly);
const Bytef *p = d->zStream.next_in;
ulong len = d->zStream.avail_in;
#ifdef DEBUG_GZIP
qCDebug(KArchiveLog) << " calling deflate with avail_in=" << inBufferAvailable() << " avail_out=" << outBufferAvailable();
#endif
const int result = deflate(&d->zStream, finish ? Z_FINISH : Z_NO_FLUSH);
if (result != Z_OK && result != Z_STREAM_END) {
// qCDebug(KArchiveLog) << " deflate returned " << result;
}
if (d->headerWritten) {
// qCDebug(KArchiveLog) << "Computing CRC for the next " << len - d->zStream.avail_in << " bytes";
d->crc = crc32(d->crc, p, len - d->zStream.avail_in);
}
KGzipFilter::Result callerResult = result == Z_OK ? KFilterBase::Ok : (Z_STREAM_END ? KFilterBase::End : KFilterBase::Error);
if (result == Z_STREAM_END && d->headerWritten && !d->footerWritten) {
if (d->zStream.avail_out >= 8 /*footer size*/) {
// qCDebug(KArchiveLog) << "finished, write footer";
writeFooter();
} else {
// No room to write the footer (#157706/#188415), we'll have to do it on the next pass.
// qCDebug(KArchiveLog) << "finished, but no room for footer yet";
callerResult = KFilterBase::Ok;
}
}
return callerResult;
}
@@ -0,0 +1,59 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000, 2009 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef __kgzipfilter__h
#define __kgzipfilter__h
#include "kfilterbase.h"
/**
* Internal class used by KCompressionDevice
*
* This header is not installed.
*
* @internal
*/
class KGzipFilter : public KFilterBase
{
public:
KGzipFilter();
~KGzipFilter() override;
bool init(int mode) override;
// The top of zlib.h explains it: there are three cases.
// - Raw deflate, no header (e.g. inside a ZIP file)
// - Thin zlib header (1) (which is normally what HTTP calls "deflate" (2))
// - Gzip header, implemented here by readHeader
//
// (1) as written out by compress()/compress2()
// (2) see https://www.zlib.net/zlib_faq.html#faq39
enum Flag {
RawDeflate = 0, // raw deflate data
ZlibHeader = 1, // zlib headers (HTTP deflate)
GZipHeader = 2,
};
bool init(int mode, Flag flag); // for direct users of KGzipFilter
int mode() const override;
bool terminate() override;
void reset() override;
bool readHeader() override; // this is about the GZIP header
bool writeHeader(const QByteArray &fileName) override;
void writeFooter();
void setOutBuffer(char *data, uint maxlen) override;
void setInBuffer(const char *data, uint size) override;
int inBufferAvailable() const override;
int outBufferAvailable() const override;
Result uncompress() override;
Result compress(bool finish) override;
private:
Result uncompress_noop();
class Private;
Private *const d;
};
#endif
@@ -0,0 +1,73 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2001, 2002, 2007 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "klimitediodevice_p.h"
#include "loggingcategory.h"
KLimitedIODevice::KLimitedIODevice(QIODevice *dev, qint64 start, qint64 length)
: m_dev(dev)
, m_start(start)
, m_length(length)
{
// qCDebug(KArchiveLog) << "start=" << start << "length=" << length;
open(QIODevice::ReadOnly); // krazy:exclude=syscalls
}
bool KLimitedIODevice::open(QIODevice::OpenMode m)
{
// qCDebug(KArchiveLog) << "m=" << m;
if (m & QIODevice::ReadOnly) {
/*bool ok = false;
if ( m_dev->isOpen() )
ok = ( m_dev->mode() == QIODevice::ReadOnly );
else
ok = m_dev->open( m );
if ( ok )*/
m_dev->seek(m_start); // No concurrent access !
} else {
// qCWarning(KArchiveLog) << "KLimitedIODevice::open only supports QIODevice::ReadOnly!";
}
setOpenMode(QIODevice::ReadOnly);
return true;
}
void KLimitedIODevice::close()
{
}
qint64 KLimitedIODevice::size() const
{
return m_length;
}
qint64 KLimitedIODevice::readData(char *data, qint64 maxlen)
{
maxlen = qMin(maxlen, m_length - pos()); // Apply upper limit
return m_dev->read(data, maxlen);
}
bool KLimitedIODevice::seek(qint64 pos)
{
Q_ASSERT(pos <= m_length);
pos = qMin(pos, m_length); // Apply upper limit
bool ret = m_dev->seek(m_start + pos);
if (ret) {
QIODevice::seek(pos);
}
return ret;
}
qint64 KLimitedIODevice::bytesAvailable() const
{
return QIODevice::bytesAvailable();
}
bool KLimitedIODevice::isSequential() const
{
return m_dev->isSequential();
}
#include "moc_klimitediodevice_p.cpp"
@@ -0,0 +1,58 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2001, 2002, 2007 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef klimitediodevice_p_h
#define klimitediodevice_p_h
#include <QDebug>
#include <QIODevice>
/**
* A readonly device that reads from an underlying device
* from a given point to another (e.g. to give access to a single
* file inside an archive).
* @author David Faure <faure@kde.org>
* @internal - used by KArchive
*/
class KLimitedIODevice : public QIODevice
{
Q_OBJECT
public:
/**
* Creates a new KLimitedIODevice.
* @param dev the underlying device, opened or not
* This device itself auto-opens (in readonly mode), no need to open it.
* @param start where to start reading (position in bytes)
* @param length the length of the data to read (in bytes)
*/
KLimitedIODevice(QIODevice *dev, qint64 start, qint64 length);
~KLimitedIODevice() override
{
}
bool isSequential() const override;
bool open(QIODevice::OpenMode m) override;
void close() override;
qint64 size() const override;
qint64 readData(char *data, qint64 maxlen) override;
qint64 writeData(const char *, qint64) override
{
return -1; // unsupported
}
// virtual qint64 pos() const { return m_dev->pos() - m_start; }
bool seek(qint64 pos) override;
qint64 bytesAvailable() const override;
private:
QIODevice *m_dev;
qint64 m_start;
qint64 m_length;
};
#endif
@@ -0,0 +1,127 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2011 Mario Bensi <mbensi@ipsquad.net>
Based on kbzip2filter:
SPDX-FileCopyrightText: 2000, 2009 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "knonefilter.h"
#include <QFile>
class Q_DECL_HIDDEN KNoneFilter::Private
{
public:
Private()
: mode(0)
, avail_out(0)
, avail_in(0)
, next_in(nullptr)
, next_out(nullptr)
{
}
int mode;
int avail_out;
int avail_in;
const char *next_in;
char *next_out;
};
KNoneFilter::KNoneFilter()
: d(new Private)
{
}
KNoneFilter::~KNoneFilter()
{
delete d;
}
bool KNoneFilter::init(int mode)
{
d->mode = mode;
return true;
}
int KNoneFilter::mode() const
{
return d->mode;
}
bool KNoneFilter::terminate()
{
return true;
}
void KNoneFilter::reset()
{
}
bool KNoneFilter::readHeader()
{
return true;
}
bool KNoneFilter::writeHeader(const QByteArray & /*fileName*/)
{
return true;
}
void KNoneFilter::setOutBuffer(char *data, uint maxlen)
{
d->avail_out = maxlen;
d->next_out = data;
}
void KNoneFilter::setInBuffer(const char *data, uint size)
{
d->next_in = data;
d->avail_in = size;
}
int KNoneFilter::inBufferAvailable() const
{
return d->avail_in;
}
int KNoneFilter::outBufferAvailable() const
{
return d->avail_out;
}
KNoneFilter::Result KNoneFilter::uncompress()
{
#ifndef NDEBUG
if (d->mode != QIODevice::ReadOnly) {
return KFilterBase::Error;
}
#endif
return copyData();
}
KNoneFilter::Result KNoneFilter::compress(bool finish)
{
Q_ASSERT(d->mode == QIODevice::WriteOnly);
Q_UNUSED(finish);
return copyData();
}
KNoneFilter::Result KNoneFilter::copyData()
{
Q_ASSERT(d->avail_out > 0);
if (d->avail_in > 0) {
const int n = qMin(d->avail_in, d->avail_out);
memcpy(d->next_out, d->next_in, n);
d->avail_out -= n;
d->next_in += n;
d->next_out += n;
d->avail_in -= n;
return KFilterBase::Ok;
} else {
return KFilterBase::End;
}
}
@@ -0,0 +1,48 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2011 Mario Bensi <mbensi@ipsquad.net>
Based on kbzip2filter:
SPDX-FileCopyrightText: 2000, 2009 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef __knonefilter__h
#define __knonefilter__h
#include "kfilterbase.h"
/**
* Internal class used by KCompressionDevice
*
* This header is not installed.
*
* @internal
*/
class KNoneFilter : public KFilterBase
{
public:
KNoneFilter();
~KNoneFilter() override;
bool init(int mode) override;
int mode() const override;
bool terminate() override;
void reset() override;
bool readHeader() override; // this is about the GZIP header
bool writeHeader(const QByteArray &fileName) override;
void setOutBuffer(char *data, uint maxlen) override;
void setInBuffer(const char *data, uint size) override;
int inBufferAvailable() const override;
int outBufferAvailable() const override;
Result uncompress() override;
Result compress(bool finish) override;
private:
Result copyData();
class Private;
Private *const d;
};
#endif
@@ -0,0 +1,162 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2014 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "krcc.h"
#include "karchive_p.h"
#include "loggingcategory.h"
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QResource>
#include <QUuid>
class Q_DECL_HIDDEN KRcc::KRccPrivate
{
public:
KRccPrivate()
{
}
void createEntries(const QDir &dir, KArchiveDirectory *parentDir, KRcc *q);
QString m_prefix; // '/' + uuid
};
/**
* A KRccFileEntry represents a file in a rcc archive.
*/
class KRccFileEntry : public KArchiveFile
{
public:
KRccFileEntry(KArchive *archive,
const QString &name,
int access,
const QDateTime &date,
const QString &user,
const QString &group,
qint64 size,
const QString &resourcePath)
: KArchiveFile(archive, name, access, date, user, group, QString(), 0, size)
, m_resourcePath(resourcePath)
{
}
QByteArray data() const override
{
QFile f(m_resourcePath);
if (f.open(QIODevice::ReadOnly)) {
return f.readAll();
}
qCWarning(KArchiveLog) << "Couldn't open" << m_resourcePath;
return QByteArray();
}
QIODevice *createDevice() const override
{
return new QFile(m_resourcePath);
}
private:
QString m_resourcePath;
};
KRcc::KRcc(const QString &filename)
: KArchive(filename)
, d(new KRccPrivate)
{
}
KRcc::~KRcc()
{
if (isOpen()) {
close();
}
delete d;
}
bool KRcc::doPrepareWriting(const QString &, const QString &, const QString &, qint64, mode_t, const QDateTime &, const QDateTime &, const QDateTime &)
{
setErrorString(tr("Cannot write to RCC file"));
qCWarning(KArchiveLog) << "doPrepareWriting not implemented for KRcc";
return false;
}
bool KRcc::doFinishWriting(qint64)
{
setErrorString(tr("Cannot write to RCC file"));
qCWarning(KArchiveLog) << "doFinishWriting not implemented for KRcc";
return false;
}
bool KRcc::doWriteDir(const QString &, const QString &, const QString &, mode_t, const QDateTime &, const QDateTime &, const QDateTime &)
{
setErrorString(tr("Cannot write to RCC file"));
qCWarning(KArchiveLog) << "doWriteDir not implemented for KRcc";
return false;
}
bool KRcc::doWriteSymLink(const QString &, const QString &, const QString &, const QString &, mode_t, const QDateTime &, const QDateTime &, const QDateTime &)
{
setErrorString(tr("Cannot write to RCC file"));
qCWarning(KArchiveLog) << "doWriteSymLink not implemented for KRcc";
return false;
}
bool KRcc::openArchive(QIODevice::OpenMode mode)
{
// Open archive
if (mode == QIODevice::WriteOnly) {
return true;
}
if (mode != QIODevice::ReadOnly && mode != QIODevice::ReadWrite) {
setErrorString(tr("Unsupported mode %1").arg(static_cast<int>(mode)));
return false;
}
QUuid uuid = QUuid::createUuid();
d->m_prefix = QLatin1Char('/') + uuid.toString();
if (!QResource::registerResource(fileName(), d->m_prefix)) {
setErrorString(tr("Failed to register resource %1 under prefix %2").arg(fileName(), d->m_prefix));
return false;
}
QDir dir(QLatin1Char(':') + d->m_prefix);
d->createEntries(dir, rootDir(), this);
return true;
}
void KRcc::KRccPrivate::createEntries(const QDir &dir, KArchiveDirectory *parentDir, KRcc *q)
{
for (const QString &fileName : dir.entryList()) {
const QString entryPath = dir.path() + QLatin1Char('/') + fileName;
const QFileInfo info(entryPath);
if (info.isFile()) {
KArchiveEntry *entry = new KRccFileEntry(q, fileName, 0444, info.lastModified(), parentDir->user(), parentDir->group(), info.size(), entryPath);
parentDir->addEntry(entry);
} else {
KArchiveDirectory *entry =
new KArchiveDirectory(q, fileName, 0555, info.lastModified(), parentDir->user(), parentDir->group(), /*symlink*/ QString());
if (parentDir->addEntryV2(entry)) {
createEntries(QDir(entryPath), entry, q);
}
}
}
}
bool KRcc::closeArchive()
{
// Close the archive
QResource::unregisterResource(fileName(), d->m_prefix);
// ignore errors
return true;
}
void KRcc::virtual_hook(int id, void *data)
{
KArchive::virtual_hook(id, data);
}
@@ -0,0 +1,100 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2014 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KRCC_H
#define KRCC_H
#include <karchive.h>
/**
* KRcc is a class for reading dynamic binary resources created by Qt's rcc tool
* from a .qrc file and the files it points to.
*
* Writing is not supported.
* @short A class for reading rcc resources.
* @since 5.3
*/
class KARCHIVE_EXPORT KRcc : public KArchive
{
Q_DECLARE_TR_FUNCTIONS(KRcc)
public:
/**
* Creates an instance that operates on the given filename.
*
* @param filename is a local path (e.g. "/home/holger/myfile.rcc")
*/
explicit KRcc(const QString &filename);
/**
* If the rcc file is still opened, then it will be
* closed automatically by the destructor.
*/
~KRcc() override;
protected:
/*
* Writing is not supported by this class, will always fail.
* @return always false
*/
bool doPrepareWriting(const QString &name,
const QString &user,
const QString &group,
qint64 size,
mode_t perm,
const QDateTime &atime,
const QDateTime &mtime,
const QDateTime &ctime) override;
/*
* Writing is not supported by this class, will always fail.
* @return always false
*/
bool doFinishWriting(qint64 size) override;
/*
* Writing is not supported by this class, will always fail.
* @return always false
*/
bool doWriteDir(const QString &name,
const QString &user,
const QString &group,
mode_t perm,
const QDateTime &atime,
const QDateTime &mtime,
const QDateTime &ctime) override;
/*
* Writing is not supported by this class, will always fail.
* @return always false
*/
bool doWriteSymLink(const QString &name,
const QString &target,
const QString &user,
const QString &group,
mode_t perm,
const QDateTime &atime,
const QDateTime &mtime,
const QDateTime &ctime) override;
/**
* Registers the .rcc resource in the QResource system under a unique identifier,
* then lists that, and creates the KArchiveFile/KArchiveDirectory entries.
*/
bool openArchive(QIODevice::OpenMode mode) override;
/**
* Unregisters the .rcc resource from the QResource system.
*/
bool closeArchive() override;
protected:
void virtual_hook(int id, void *data) override;
private:
class KRccPrivate;
KRccPrivate *const d;
};
#endif
@@ -0,0 +1,975 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2003 Leo Savernik <l.savernik@aon.at>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "ktar.h"
#include "karchive_p.h"
#include "kcompressiondevice.h"
#include "kfilterbase.h"
#include "loggingcategory.h"
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QMimeDatabase>
#include <QTemporaryFile>
#include <assert.h>
#include <stdlib.h> // strtol
////////////////////////////////////////////////////////////////////////
/////////////////////////// KTar ///////////////////////////////////
////////////////////////////////////////////////////////////////////////
// Mime types of known filters
static const char application_bzip[] = "application/x-bzip";
static const char application_lzma[] = "application/x-lzma";
static const char application_xz[] = "application/x-xz";
static const char application_zstd[] = "application/zstd";
/* clang-format off */
namespace MimeType
{
QString application_gzip() { return QStringLiteral("application/gzip"); }
QString application_gzip_old() { return QStringLiteral("application/x-gzip"); }
}
/* clang-format on */
class Q_DECL_HIDDEN KTar::KTarPrivate
{
public:
KTarPrivate(KTar *parent)
: q(parent)
, tarEnd(0)
, tmpFile(nullptr)
, compressionDevice(nullptr)
{
}
KTar *q;
QStringList dirList;
qint64 tarEnd;
QTemporaryFile *tmpFile;
QString mimetype;
QByteArray origFileName;
KCompressionDevice *compressionDevice;
bool fillTempFile(const QString &fileName);
bool writeBackTempFile(const QString &fileName);
void fillBuffer(char *buffer, const char *mode, qint64 size, const QDateTime &mtime, char typeflag, const char *uname, const char *gname);
void writeLonglink(char *buffer, const QByteArray &name, char typeflag, const char *uname, const char *gname);
qint64 readRawHeader(char *buffer);
bool readLonglink(char *buffer, QByteArray &longlink);
qint64 readHeader(char *buffer, QString &name, QString &symlink);
};
KTar::KTar(const QString &fileName, const QString &_mimetype)
: KArchive(fileName)
, d(new KTarPrivate(this))
{
// shared-mime-info < 1.1 does not know about application/gzip.
// While Qt has optionally a copy of shared-mime-info (1.10 for 5.15.0),
// it uses the system one if it exists.
// Once shared-mime-info 1.1 is required or can be assumed on all targeted
// platforms (right now RHEL/CentOS 6 as target of appimage-based apps
// bundling also karchive does not meet this requirement)
// switch to use the new application/gzip id instead when interacting with QMimeDatabase
// For now: map new name to legacy name and use that
d->mimetype = (_mimetype == MimeType::application_gzip()) ? MimeType::application_gzip_old() : _mimetype;
}
KTar::KTar(QIODevice *dev)
: KArchive(dev)
, d(new KTarPrivate(this))
{
}
// Only called when a filename was given
bool KTar::createDevice(QIODevice::OpenMode mode)
{
if (d->mimetype.isEmpty()) {
// Find out mimetype manually
QMimeDatabase db;
QMimeType mime;
if (mode != QIODevice::WriteOnly && QFile::exists(fileName())) {
// Give priority to file contents: if someone renames a .tar.bz2 to .tar.gz,
// we can still do the right thing here.
QFile f(fileName());
if (f.open(QIODevice::ReadOnly)) {
mime = db.mimeTypeForData(&f);
}
if (!mime.isValid()) {
// Unable to determine mimetype from contents, get it from file name
mime = db.mimeTypeForFile(fileName(), QMimeDatabase::MatchExtension);
}
} else {
mime = db.mimeTypeForFile(fileName(), QMimeDatabase::MatchExtension);
}
// qCDebug(KArchiveLog) << mode << mime->name();
if (mime.inherits(QStringLiteral("application/x-compressed-tar")) || mime.inherits(MimeType::application_gzip_old())) {
// gzipped tar file (with possibly invalid file name), ask for gzip filter
d->mimetype = MimeType::application_gzip_old();
} else if (mime.inherits(QStringLiteral("application/x-bzip-compressed-tar")) || mime.inherits(QStringLiteral("application/x-bzip2-compressed-tar"))
|| mime.inherits(QStringLiteral("application/x-bzip2")) || mime.inherits(QString::fromLatin1(application_bzip))) {
// bzipped2 tar file (with possibly invalid file name), ask for bz2 filter
d->mimetype = QString::fromLatin1(application_bzip);
} else if (mime.inherits(QStringLiteral("application/x-lzma-compressed-tar")) || mime.inherits(QString::fromLatin1(application_lzma))) {
// lzma compressed tar file (with possibly invalid file name), ask for xz filter
d->mimetype = QString::fromLatin1(application_lzma);
} else if (mime.inherits(QStringLiteral("application/x-xz-compressed-tar")) || mime.inherits(QString::fromLatin1(application_xz))) {
// xz compressed tar file (with possibly invalid name), ask for xz filter
d->mimetype = QString::fromLatin1(application_xz);
} else if (mime.inherits(QStringLiteral("application/x-zstd-compressed-tar")) || mime.inherits(QString::fromLatin1(application_zstd))) {
// zstd compressed tar file (with possibly invalid name), ask for zstd filter
d->mimetype = QString::fromLatin1(application_zstd);
}
}
if (d->mimetype == QLatin1String("application/x-tar")) {
return KArchive::createDevice(mode);
} else if (mode == QIODevice::WriteOnly) {
if (!KArchive::createDevice(mode)) {
return false;
}
if (!d->mimetype.isEmpty()) {
// Create a compression filter on top of the QSaveFile device that KArchive created.
// qCDebug(KArchiveLog) << "creating KCompressionDevice for" << d->mimetype;
KCompressionDevice::CompressionType type = KCompressionDevice::compressionTypeForMimeType(d->mimetype);
d->compressionDevice = new KCompressionDevice(device(), false, type);
setDevice(d->compressionDevice);
}
return true;
} else {
// The compression filters are very slow with random access.
// So instead of applying the filter to the device,
// the file is completely extracted instead,
// and we work on the extracted tar file.
// This improves the extraction speed by the archive KIO worker supporting the tar protocol dramatically,
// if the archive file contains many files.
// This is because the archive KIO worker extracts one file after the other and normally
// has to walk through the decompression filter each time.
// Which is in fact nearly as slow as a complete decompression for each file.
Q_ASSERT(!d->tmpFile);
d->tmpFile = new QTemporaryFile();
d->tmpFile->setFileTemplate(QDir::tempPath() + QLatin1Char('/') + QLatin1String("ktar-XXXXXX.tar"));
d->tmpFile->open();
// qCDebug(KArchiveLog) << "creating tempfile:" << d->tmpFile->fileName();
setDevice(d->tmpFile);
return true;
}
}
KTar::~KTar()
{
// mjarrett: Closes to prevent ~KArchive from aborting w/o device
if (isOpen()) {
close();
}
delete d->tmpFile;
delete d->compressionDevice;
delete d;
}
void KTar::setOrigFileName(const QByteArray &fileName)
{
if (!isOpen() || !(mode() & QIODevice::WriteOnly)) {
// qCWarning(KArchiveLog) << "KTar::setOrigFileName: File must be opened for writing first.\n";
return;
}
d->origFileName = fileName;
}
qint64 KTar::KTarPrivate::readRawHeader(char *buffer)
{
// Read header
qint64 n = q->device()->read(buffer, 0x200);
// we need to test if there is a prefix value because the file name can be null
// and the prefix can have a value and in this case we don't reset n.
if (n == 0x200 && (buffer[0] != 0 || buffer[0x159] != 0)) {
// Make sure this is actually a tar header
if (strncmp(buffer + 257, "ustar", 5)) {
// The magic isn't there (broken/old tars), but maybe a correct checksum?
int check = 0;
for (uint j = 0; j < 0x200; ++j) {
check += static_cast<unsigned char>(buffer[j]);
}
// adjust checksum to count the checksum fields as blanks
for (uint j = 0; j < 8 /*size of the checksum field including the \0 and the space*/; j++) {
check -= static_cast<unsigned char>(buffer[148 + j]);
}
check += 8 * ' ';
QByteArray s = QByteArray::number(check, 8); // octal
// only compare those of the 6 checksum digits that mean something,
// because the other digits are filled with all sorts of different chars by different tars ...
// Some tars right-justify the checksum so it could start in one of three places - we have to check each.
if (strncmp(buffer + 148 + 6 - s.length(), s.data(), s.length()) //
&& strncmp(buffer + 148 + 7 - s.length(), s.data(), s.length()) //
&& strncmp(buffer + 148 + 8 - s.length(), s.data(), s.length())) {
/*qCWarning(KArchiveLog) << "KTar: invalid TAR file. Header is:" << QByteArray( buffer+257, 5 )
<< "instead of ustar. Reading from wrong pos in file?"
<< "checksum=" << QByteArray( buffer + 148 + 6 - s.length(), s.length() );*/
return -1;
}
} /*end if*/
} else {
// reset to 0 if 0x200 because logical end of archive has been reached
if (n == 0x200) {
n = 0;
}
} /*end if*/
return n;
}
bool KTar::KTarPrivate::readLonglink(char *buffer, QByteArray &longlink)
{
qint64 n = 0;
// qCDebug(KArchiveLog) << "reading longlink from pos " << q->device()->pos();
QIODevice *dev = q->device();
// read size of longlink from size field in header
// size is in bytes including the trailing null (which we ignore)
qint64 size = QByteArray(buffer + 0x7c, 12).trimmed().toLongLong(nullptr, 8 /*octal*/);
size--; // ignore trailing null
if (size > std::numeric_limits<int>::max() - 32) { // QByteArray can't really be INT_MAX big, it's max size is something between INT_MAX - 32 and INT_MAX
// depending the platform so just be safe
qCWarning(KArchiveLog) << "Failed to allocate memory for longlink of size" << size;
return false;
}
if (size < 0) {
qCWarning(KArchiveLog) << "Invalid longlink size" << size;
return false;
}
longlink.resize(size);
qint64 offset = 0;
while (size > 0) {
int chunksize = qMin(size, 0x200LL);
n = dev->read(longlink.data() + offset, chunksize);
if (n == -1) {
return false;
}
size -= chunksize;
offset += 0x200;
} /*wend*/
// jump over the rest
const int skip = 0x200 - (n % 0x200);
if (skip <= 0x200) {
if (dev->read(buffer, skip) != skip) {
return false;
}
}
longlink.truncate(qstrlen(longlink.constData()));
return true;
}
qint64 KTar::KTarPrivate::readHeader(char *buffer, QString &name, QString &symlink)
{
name.truncate(0);
symlink.truncate(0);
while (true) {
qint64 n = readRawHeader(buffer);
if (n != 0x200) {
return n;
}
// is it a longlink?
if (strcmp(buffer, "././@LongLink") == 0) {
char typeflag = buffer[0x9c];
QByteArray longlink;
if (readLonglink(buffer, longlink)) {
switch (typeflag) {
case 'L':
name = QFile::decodeName(longlink.constData());
break;
case 'K':
symlink = QFile::decodeName(longlink.constData());
break;
} /*end switch*/
}
} else {
break;
} /*end if*/
} /*wend*/
// if not result of longlink, read names directly from the header
if (name.isEmpty())
// there are names that are exactly 100 bytes long
// and neither longlink nor \0 terminated (bug:101472)
{
name = QFile::decodeName(QByteArray(buffer, qstrnlen(buffer, 100)));
}
if (symlink.isEmpty()) {
char *symlinkBuffer = buffer + 0x9d /*?*/;
symlink = QFile::decodeName(QByteArray(symlinkBuffer, qstrnlen(symlinkBuffer, 100)));
}
return 0x200;
}
/*
* If we have created a temporary file, we have
* to decompress the original file now and write
* the contents to the temporary file.
*/
bool KTar::KTarPrivate::fillTempFile(const QString &fileName)
{
if (!tmpFile) {
return true;
}
// qCDebug(KArchiveLog) << "filling tmpFile of mimetype" << mimetype;
KCompressionDevice::CompressionType compressionType = KCompressionDevice::compressionTypeForMimeType(mimetype);
KCompressionDevice filterDev(fileName, compressionType);
QFile *file = tmpFile;
Q_ASSERT(file->isOpen());
Q_ASSERT(file->openMode() & QIODevice::WriteOnly);
file->seek(0);
QByteArray buffer;
buffer.resize(8 * 1024);
if (!filterDev.open(QIODevice::ReadOnly)) {
q->setErrorString(tr("File %1 does not exist").arg(fileName));
return false;
}
qint64 len = -1;
while (!filterDev.atEnd() && len != 0) {
len = filterDev.read(buffer.data(), buffer.size());
if (len < 0) { // corrupted archive
q->setErrorString(tr("Archive %1 is corrupt").arg(fileName));
return false;
}
if (file->write(buffer.data(), len) != len) { // disk full
q->setErrorString(tr("Disk full"));
return false;
}
}
filterDev.close();
file->flush();
file->seek(0);
Q_ASSERT(file->isOpen());
Q_ASSERT(file->openMode() & QIODevice::ReadOnly);
// qCDebug(KArchiveLog) << "filling tmpFile finished.";
return true;
}
bool KTar::openArchive(QIODevice::OpenMode mode)
{
if (!(mode & QIODevice::ReadOnly)) {
return true;
}
if (!d->fillTempFile(fileName())) {
return false;
}
// We'll use the permission and user/group of d->rootDir
// for any directory we emulate (see findOrCreate)
// struct stat buf;
// stat( fileName(), &buf );
d->dirList.clear();
QIODevice *dev = device();
if (!dev) {
setErrorString(tr("Could not get underlying device"));
qCWarning(KArchiveLog) << "Could not get underlying device";
return false;
}
// read dir information
char buffer[0x200];
bool ende = false;
do {
QString name;
QString symlink;
// Read header
qint64 n = d->readHeader(buffer, name, symlink);
if (n < 0) {
setErrorString(tr("Could not read tar header"));
return false;
}
if (n == 0x200) {
bool isdir = false;
if (name.isEmpty()) {
continue;
}
if (name.endsWith(QLatin1Char('/'))) {
isdir = true;
name.truncate(name.length() - 1);
}
QByteArray prefix = QByteArray(buffer + 0x159, 155);
if (prefix[0] != '\0') {
name = (QString::fromLatin1(prefix.constData()) + QLatin1Char('/') + name);
}
int pos = name.lastIndexOf(QLatin1Char('/'));
QString nm = (pos == -1) ? name : name.mid(pos + 1);
// read access
buffer[0x6b] = 0;
char *dummy;
const char *p = buffer + 0x64;
while (*p == ' ') {
++p;
}
int access = strtol(p, &dummy, 8);
// read user and group
const int maxUserGroupLength = 32;
const char *userStart = buffer + 0x109;
const int userLen = qstrnlen(userStart, maxUserGroupLength);
const QString user = QString::fromLocal8Bit(userStart, userLen);
const char *groupStart = buffer + 0x129;
const int groupLen = qstrnlen(groupStart, maxUserGroupLength);
const QString group = QString::fromLocal8Bit(groupStart, groupLen);
// read time
buffer[0x93] = 0;
p = buffer + 0x88;
while (*p == ' ') {
++p;
}
uint time = strtol(p, &dummy, 8);
// read type flag
char typeflag = buffer[0x9c];
// '0' for files, '1' hard link, '2' symlink, '5' for directory
// (and 'L' for longlink fileNames, 'K' for longlink symlink targets)
// 'D' for GNU tar extension DUMPDIR, 'x' for Extended header referring
// to the next file in the archive and 'g' for Global extended header
if (typeflag == '5') {
isdir = true;
}
bool isDumpDir = false;
if (typeflag == 'D') {
isdir = false;
isDumpDir = true;
}
// qCDebug(KArchiveLog) << nm << "isdir=" << isdir << "pos=" << dev->pos() << "typeflag=" << typeflag << " islink=" << ( typeflag == '1' || typeflag
// == '2' );
if (typeflag == 'x' || typeflag == 'g') { // pax extended header, or pax global extended header
// Skip it for now. TODO: implement reading of extended header, as per https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html
(void)dev->read(buffer, 0x200);
continue;
}
if (isdir) {
access |= S_IFDIR; // broken tar files...
}
KArchiveEntry *e;
if (isdir) {
// qCDebug(KArchiveLog) << "directory" << nm;
e = new KArchiveDirectory(this, nm, access, KArchivePrivate::time_tToDateTime(time), user, group, symlink);
} else {
// read size
QByteArray sizeBuffer(buffer + 0x7c, 12);
qint64 size = sizeBuffer.trimmed().toLongLong(nullptr, 8 /*octal*/);
if (size < 0) {
qWarning() << "Tar file has negative size, resetting to 0";
size = 0;
}
// qCDebug(KArchiveLog) << "sizeBuffer='" << sizeBuffer << "' -> size=" << size;
// for isDumpDir we will skip the additional info about that dirs contents
if (isDumpDir) {
// qCDebug(KArchiveLog) << nm << "isDumpDir";
e = new KArchiveDirectory(this, nm, access, KArchivePrivate::time_tToDateTime(time), user, group, symlink);
} else {
// Let's hack around hard links. Our classes don't support that, so make them symlinks
if (typeflag == '1') {
// qCDebug(KArchiveLog) << "Hard link, setting size to 0 instead of" << size;
size = 0; // no contents
}
// qCDebug(KArchiveLog) << "file" << nm << "size=" << size;
e = new KArchiveFile(this, nm, access, KArchivePrivate::time_tToDateTime(time), user, group, symlink, dev->pos(), size);
}
// Skip contents + align bytes
qint64 rest = size % 0x200;
qint64 skip = size + (rest ? 0x200 - rest : 0);
// qCDebug(KArchiveLog) << "pos()=" << dev->pos() << "rest=" << rest << "skipping" << skip;
if (!dev->seek(dev->pos() + skip)) {
// qCWarning(KArchiveLog) << "skipping" << skip << "failed";
}
}
if (pos == -1) {
if (nm == QLatin1String(".")) { // special case
if (isdir) {
if (KArchivePrivate::hasRootDir(this)) {
qWarning() << "Broken tar file has two root dir entries";
delete e;
} else {
setRootDir(static_cast<KArchiveDirectory *>(e));
}
} else {
delete e;
}
} else {
rootDir()->addEntry(e);
}
} else {
// In some tar files we can find dir/./file => call cleanPath
QString path = QDir::cleanPath(name.left(pos));
// Ensure container directory exists, create otherwise
KArchiveDirectory *d = findOrCreate(path);
if (d) {
d->addEntry(e);
} else {
delete e;
return false;
}
}
} else {
// qCDebug(KArchiveLog) << "Terminating. Read " << n << " bytes, first one is " << buffer[0];
d->tarEnd = dev->pos() - n; // Remember end of archive
ende = true;
}
} while (!ende);
return true;
}
/*
* Writes back the changes of the temporary file
* to the original file.
* Must only be called if in write mode, not in read mode
*/
bool KTar::KTarPrivate::writeBackTempFile(const QString &fileName)
{
if (!tmpFile) {
return true;
}
// qCDebug(KArchiveLog) << "Write temporary file to compressed file" << fileName << mimetype;
bool forced = false;
/* clang-format off */
if (MimeType::application_gzip_old() == mimetype ||
QLatin1String(application_bzip) == mimetype ||
QLatin1String(application_lzma) == mimetype ||
QLatin1String(application_xz) == mimetype) {
/* clang-format on */
forced = true;
}
// #### TODO this should use QSaveFile to avoid problems on disk full
// (KArchive uses QSaveFile by default, but the temp-uncompressed-file trick
// circumvents that).
KCompressionDevice dev(fileName);
QFile *file = tmpFile;
if (!dev.open(QIODevice::WriteOnly)) {
file->close();
q->setErrorString(tr("Failed to write back temp file: %1").arg(dev.errorString()));
return false;
}
if (forced) {
dev.setOrigFileName(origFileName);
}
file->seek(0);
QByteArray buffer;
buffer.resize(8 * 1024);
qint64 len;
while (!file->atEnd()) {
len = file->read(buffer.data(), buffer.size());
dev.write(buffer.data(), len); // TODO error checking
}
file->close();
dev.close();
// qCDebug(KArchiveLog) << "Write temporary file to compressed file done.";
return true;
}
bool KTar::closeArchive()
{
d->dirList.clear();
bool ok = true;
// If we are in readwrite mode and had created
// a temporary tar file, we have to write
// back the changes to the original file
if (d->tmpFile && (mode() & QIODevice::WriteOnly)) {
ok = d->writeBackTempFile(fileName());
delete d->tmpFile;
d->tmpFile = nullptr;
setDevice(nullptr);
}
return ok;
}
bool KTar::doFinishWriting(qint64 size)
{
// Write alignment
int rest = size % 0x200;
if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) {
d->tarEnd = device()->pos() + (rest ? 0x200 - rest : 0); // Record our new end of archive
}
if (rest) {
char buffer[0x201];
for (uint i = 0; i < 0x200; ++i) {
buffer[i] = 0;
}
qint64 nwritten = device()->write(buffer, 0x200 - rest);
const bool ok = nwritten == 0x200 - rest;
if (!ok) {
setErrorString(tr("Couldn't write alignment: %1").arg(device()->errorString()));
}
return ok;
}
return true;
}
/*** Some help from the tar sources
struct posix_header
{ byte offset
char name[100]; * 0 * 0x0
char mode[8]; * 100 * 0x64
char uid[8]; * 108 * 0x6c
char gid[8]; * 116 * 0x74
char size[12]; * 124 * 0x7c
char mtime[12]; * 136 * 0x88
char chksum[8]; * 148 * 0x94
char typeflag; * 156 * 0x9c
char linkname[100]; * 157 * 0x9d
char magic[6]; * 257 * 0x101
char version[2]; * 263 * 0x107
char uname[32]; * 265 * 0x109
char gname[32]; * 297 * 0x129
char devmajor[8]; * 329 * 0x149
char devminor[8]; * 337 * ...
char prefix[155]; * 345 *
* 500 *
};
*/
void KTar::KTarPrivate::fillBuffer(char *buffer, const char *mode, qint64 size, const QDateTime &mtime, char typeflag, const char *uname, const char *gname)
{
// mode (as in stpos())
assert(strlen(mode) == 6);
memcpy(buffer + 0x64, mode, 6);
buffer[0x6a] = ' ';
buffer[0x6b] = '\0';
// dummy uid
strcpy(buffer + 0x6c, " 765 "); // 501 in decimal
// dummy gid
strcpy(buffer + 0x74, " 144 "); // 100 in decimal
// size
QByteArray s = QByteArray::number(size, 8); // octal
s = s.rightJustified(11, '0');
memcpy(buffer + 0x7c, s.data(), 11);
buffer[0x87] = ' '; // space-terminate (no null after)
// modification time
const QDateTime modificationTime = mtime.isValid() ? mtime : QDateTime::currentDateTime();
s = QByteArray::number(static_cast<qulonglong>(modificationTime.toMSecsSinceEpoch() / 1000), 8); // octal
s = s.rightJustified(11, '0');
memcpy(buffer + 0x88, s.data(), 11);
buffer[0x93] = ' '; // space-terminate (no null after) -- well current tar writes a null byte
// spaces, replaced by the check sum later
buffer[0x94] = 0x20;
buffer[0x95] = 0x20;
buffer[0x96] = 0x20;
buffer[0x97] = 0x20;
buffer[0x98] = 0x20;
buffer[0x99] = 0x20;
/* From the tar sources :
Fill in the checksum field. It's formatted differently from the
other fields: it has [6] digits, a null, then a space -- rather than
digits, a space, then a null. */
buffer[0x9a] = '\0';
buffer[0x9b] = ' ';
// type flag (dir, file, link)
buffer[0x9c] = typeflag;
// magic + version
strcpy(buffer + 0x101, "ustar");
strcpy(buffer + 0x107, "00");
// user
strcpy(buffer + 0x109, uname);
// group
strcpy(buffer + 0x129, gname);
// Header check sum
int check = 32;
for (uint j = 0; j < 0x200; ++j) {
check += static_cast<unsigned char>(buffer[j]);
}
s = QByteArray::number(check, 8); // octal
s = s.rightJustified(6, '0');
memcpy(buffer + 0x94, s.constData(), 6);
}
void KTar::KTarPrivate::writeLonglink(char *buffer, const QByteArray &name, char typeflag, const char *uname, const char *gname)
{
strcpy(buffer, "././@LongLink");
qint64 namelen = name.length() + 1;
fillBuffer(buffer, " 0", namelen, QDateTime(), typeflag, uname, gname);
q->device()->write(buffer, 0x200); // TODO error checking
qint64 offset = 0;
while (namelen > 0) {
int chunksize = qMin(namelen, 0x200LL);
memcpy(buffer, name.data() + offset, chunksize);
// write long name
q->device()->write(buffer, 0x200); // TODO error checking
// not even needed to reclear the buffer, tar doesn't do it
namelen -= chunksize;
offset += 0x200;
} /*wend*/
}
bool KTar::doPrepareWriting(const QString &name,
const QString &user,
const QString &group,
qint64 size,
mode_t perm,
const QDateTime & /*atime*/,
const QDateTime &mtime,
const QDateTime & /*ctime*/)
{
if (!isOpen()) {
setErrorString(tr("Application error: TAR file must be open before being written into"));
qCWarning(KArchiveLog) << "doPrepareWriting failed: !isOpen()";
return false;
}
if (!(mode() & QIODevice::WriteOnly)) {
setErrorString(tr("Application error: attempted to write into non-writable 7-Zip file"));
qCWarning(KArchiveLog) << "doPrepareWriting failed: !(mode() & QIODevice::WriteOnly)";
return false;
}
const qint64 MAX_FILESIZE = 077777777777L; // the format we use only allows 11 octal digits for size
if (size > MAX_FILESIZE) {
setErrorString(tr("Application limitation: Can not add file larger than %1 bytes").arg(MAX_FILESIZE));
return false;
}
// In some tar files we can find dir/./file => call cleanPath
QString fileName(QDir::cleanPath(name));
/*
// Create toplevel dirs
// Commented out by David since it's not necessary, and if anybody thinks it is,
// he needs to implement a findOrCreate equivalent in writeDir.
// But as KTar and the "tar" program both handle tar files without
// dir entries, there's really no need for that
QString tmp ( fileName );
int i = tmp.lastIndexOf( '/' );
if ( i != -1 )
{
QString d = tmp.left( i + 1 ); // contains trailing slash
if ( !m_dirList.contains( d ) )
{
tmp = tmp.mid( i + 1 );
writeDir( d, user, group ); // WARNING : this one doesn't create its toplevel dirs
}
}
*/
char buffer[0x201] = {0};
if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) {
device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read
}
// provide converted stuff we need later on
const QByteArray encodedFileName = QFile::encodeName(fileName);
const QByteArray uname = user.toLocal8Bit();
const QByteArray gname = group.toLocal8Bit();
// If more than 100 bytes, we need to use the LongLink trick
if (encodedFileName.length() > 99) {
d->writeLonglink(buffer, encodedFileName, 'L', uname.constData(), gname.constData());
}
// Write (potentially truncated) name
strncpy(buffer, encodedFileName.constData(), 99);
buffer[99] = 0;
// zero out the rest (except for what gets filled anyways)
memset(buffer + 0x9d, 0, 0x200 - 0x9d);
QByteArray permstr = QByteArray::number(static_cast<unsigned int>(perm), 8);
permstr = permstr.rightJustified(6, '0');
d->fillBuffer(buffer, permstr.constData(), size, mtime, 0x30, uname.constData(), gname.constData());
// Write header
if (device()->write(buffer, 0x200) != 0x200) {
setErrorString(tr("Failed to write header: %1").arg(device()->errorString()));
return false;
} else {
return true;
}
}
bool KTar::doWriteDir(const QString &name,
const QString &user,
const QString &group,
mode_t perm,
const QDateTime & /*atime*/,
const QDateTime &mtime,
const QDateTime & /*ctime*/)
{
if (!isOpen()) {
setErrorString(tr("Application error: TAR file must be open before being written into"));
qCWarning(KArchiveLog) << "doWriteDir failed: !isOpen()";
return false;
}
if (!(mode() & QIODevice::WriteOnly)) {
setErrorString(tr("Application error: attempted to write into non-writable TAR file"));
qCWarning(KArchiveLog) << "doWriteDir failed: !(mode() & QIODevice::WriteOnly)";
return false;
}
// In some tar files we can find dir/./ => call cleanPath
QString dirName(QDir::cleanPath(name));
// Need trailing '/'
if (!dirName.endsWith(QLatin1Char('/'))) {
dirName += QLatin1Char('/');
}
if (d->dirList.contains(dirName)) {
return true; // already there
}
char buffer[0x201] = {0};
if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) {
device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read
}
// provide converted stuff we need lateron
QByteArray encodedDirname = QFile::encodeName(dirName);
QByteArray uname = user.toLocal8Bit();
QByteArray gname = group.toLocal8Bit();
// If more than 100 bytes, we need to use the LongLink trick
if (encodedDirname.length() > 99) {
d->writeLonglink(buffer, encodedDirname, 'L', uname.constData(), gname.constData());
}
// Write (potentially truncated) name
strncpy(buffer, encodedDirname.constData(), 99);
buffer[99] = 0;
// zero out the rest (except for what gets filled anyways)
memset(buffer + 0x9d, 0, 0x200 - 0x9d);
QByteArray permstr = QByteArray::number(static_cast<unsigned int>(perm), 8);
permstr = permstr.rightJustified(6, ' ');
d->fillBuffer(buffer, permstr.constData(), 0, mtime, 0x35, uname.constData(), gname.constData());
// Write header
device()->write(buffer, 0x200);
if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) {
d->tarEnd = device()->pos();
}
d->dirList.append(dirName); // contains trailing slash
return true; // TODO if wanted, better error control
}
bool KTar::doWriteSymLink(const QString &name,
const QString &target,
const QString &user,
const QString &group,
mode_t perm,
const QDateTime & /*atime*/,
const QDateTime &mtime,
const QDateTime & /*ctime*/)
{
if (!isOpen()) {
setErrorString(tr("Application error: TAR file must be open before being written into"));
qCWarning(KArchiveLog) << "doWriteSymLink failed: !isOpen()";
return false;
}
if (!(mode() & QIODevice::WriteOnly)) {
setErrorString(tr("Application error: attempted to write into non-writable TAR file"));
qCWarning(KArchiveLog) << "doWriteSymLink failed: !(mode() & QIODevice::WriteOnly)";
return false;
}
// In some tar files we can find dir/./file => call cleanPath
QString fileName(QDir::cleanPath(name));
char buffer[0x201] = {0};
if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) {
device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read
}
// provide converted stuff we need lateron
QByteArray encodedFileName = QFile::encodeName(fileName);
QByteArray encodedTarget = QFile::encodeName(target);
QByteArray uname = user.toLocal8Bit();
QByteArray gname = group.toLocal8Bit();
// If more than 100 bytes, we need to use the LongLink trick
if (encodedTarget.length() > 99) {
d->writeLonglink(buffer, encodedTarget, 'K', uname.constData(), gname.constData());
}
if (encodedFileName.length() > 99) {
d->writeLonglink(buffer, encodedFileName, 'L', uname.constData(), gname.constData());
}
// Write (potentially truncated) name
strncpy(buffer, encodedFileName.constData(), 99);
buffer[99] = 0;
// Write (potentially truncated) symlink target
strncpy(buffer + 0x9d, encodedTarget.constData(), 99);
buffer[0x9d + 99] = 0;
// zero out the rest
memset(buffer + 0x9d + 100, 0, 0x200 - 100 - 0x9d);
QByteArray permstr = QByteArray::number(static_cast<unsigned int>(perm), 8);
permstr = permstr.rightJustified(6, ' ');
d->fillBuffer(buffer, permstr.constData(), 0, mtime, 0x32, uname.constData(), gname.constData());
// Write header
bool retval = device()->write(buffer, 0x200) == 0x200;
if ((mode() & QIODevice::ReadWrite) == QIODevice::ReadWrite) {
d->tarEnd = device()->pos();
}
return retval;
}
void KTar::virtual_hook(int id, void *data)
{
KArchive::virtual_hook(id, data);
}
@@ -0,0 +1,114 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000-2005 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2003 Leo Savernik <l.savernik@aon.at>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KTAR_H
#define KTAR_H
#include <karchive.h>
/**
* @class KTar ktar.h KTar
*
* A class for reading / writing (optionally compressed) tar archives.
*
* KTar allows you to read and write tar archives, including those
* that are compressed using gzip, bzip2 or xz.
*
* @author Torben Weis <weis@kde.org>, David Faure <faure@kde.org>
*/
class KARCHIVE_EXPORT KTar : public KArchive
{
Q_DECLARE_TR_FUNCTIONS(KTar)
public:
/**
* Creates an instance that operates on the given filename
* using the compression filter associated to given mimetype.
*
* @param filename is a local path (e.g. "/home/weis/myfile.tgz")
* @param mimetype "application/gzip" (before 5.85: "application/x-gzip"), "application/x-bzip",
* "application/x-xz", "application/zstd" (since 5.82)
* Do not use application/x-compressed-tar or similar - you only need to
* specify the compression layer ! If the mimetype is omitted, it
* will be determined from the filename.
*/
explicit KTar(const QString &filename, const QString &mimetype = QString());
/**
* Creates an instance that operates on the given device.
* The device can be compressed (KCompressionDevice) or not (QFile, etc.).
* @warning Do not assume that giving a QFile here will decompress the file,
* in case it's compressed!
* @param dev the device to read from. If the source is compressed, the
* QIODevice must take care of decompression
*/
explicit KTar(QIODevice *dev);
/**
* If the tar ball is still opened, then it will be
* closed automatically by the destructor.
*/
~KTar() override;
/**
* Special function for setting the "original file name" in the gzip header,
* when writing a tar.gz file. It appears when using in the "file" command,
* for instance. Should only be called if the underlying device is a KCompressionDevice!
* @param fileName the original file name
*/
void setOrigFileName(const QByteArray &fileName);
protected:
/// Reimplemented from KArchive
bool doWriteSymLink(const QString &name,
const QString &target,
const QString &user,
const QString &group,
mode_t perm,
const QDateTime &atime,
const QDateTime &mtime,
const QDateTime &ctime) override;
/// Reimplemented from KArchive
bool doWriteDir(const QString &name,
const QString &user,
const QString &group,
mode_t perm,
const QDateTime &atime,
const QDateTime &mtime,
const QDateTime &ctime) override;
/// Reimplemented from KArchive
bool doPrepareWriting(const QString &name,
const QString &user,
const QString &group,
qint64 size,
mode_t perm,
const QDateTime &atime,
const QDateTime &mtime,
const QDateTime &ctime) override;
/// Reimplemented from KArchive
bool doFinishWriting(qint64 size) override;
/**
* Opens the archive for reading.
* Parses the directory listing of the archive
* and creates the KArchiveDirectory/KArchiveFile entries.
* @param mode the mode of the file
*/
bool openArchive(QIODevice::OpenMode mode) override;
bool closeArchive() override;
bool createDevice(QIODevice::OpenMode mode) override;
private:
protected:
void virtual_hook(int id, void *data) override;
private:
class KTarPrivate;
KTarPrivate *const d;
};
#endif
@@ -0,0 +1,279 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2007-2008 Per Øyvind Karlsen <peroyvind@mandriva.org>
Based on kbzip2filter:
SPDX-FileCopyrightText: 2000-2005 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kxzfilter.h"
#include "loggingcategory.h"
#if HAVE_XZ_SUPPORT
extern "C" {
#include <lzma.h>
}
#include <QDebug>
#include <QIODevice>
class Q_DECL_HIDDEN KXzFilter::Private
{
public:
Private()
: isInitialized(false)
{
memset(&zStream, 0, sizeof(zStream));
mode = 0;
}
lzma_stream zStream;
int mode;
bool isInitialized;
KXzFilter::Flag flag;
};
KXzFilter::KXzFilter()
: d(new Private)
{
}
KXzFilter::~KXzFilter()
{
delete d;
}
bool KXzFilter::init(int mode)
{
QList<unsigned char> props;
return init(mode, AUTO, props);
}
static void freeFilters(lzma_filter filters[])
{
for (int i = 0; filters[i].id != LZMA_VLI_UNKNOWN; i++) {
free(filters[i].options);
}
}
bool KXzFilter::init(int mode, Flag flag, const QList<unsigned char> &properties)
{
if (d->isInitialized) {
terminate();
}
d->flag = flag;
lzma_ret result;
d->zStream.next_in = nullptr;
d->zStream.avail_in = 0;
if (mode == QIODevice::ReadOnly) {
// TODO when we can depend on Qt 5.12 Use a QScopeGuard to call freeFilters
lzma_filter filters[5];
filters[0].id = LZMA_VLI_UNKNOWN;
switch (flag) {
case AUTO:
/* We set the memlimit for decompression to 100MiB which should be
* more than enough to be sufficient for level 9 which requires 65 MiB.
*/
result = lzma_auto_decoder(&d->zStream, 100 << 20, 0);
if (result != LZMA_OK) {
qCWarning(KArchiveLog) << "lzma_auto_decoder returned" << result;
return false;
}
break;
case LZMA: {
filters[0].id = LZMA_FILTER_LZMA1;
filters[0].options = nullptr;
filters[1].id = LZMA_VLI_UNKNOWN;
filters[1].options = nullptr;
Q_ASSERT(properties.size() == 5);
unsigned char props[5];
for (int i = 0; i < properties.size(); ++i) {
props[i] = properties[i];
}
result = lzma_properties_decode(&filters[0], nullptr, props, sizeof(props));
if (result != LZMA_OK) {
qCWarning(KArchiveLog) << "lzma_properties_decode returned" << result;
freeFilters(filters);
return false;
}
break;
}
case LZMA2: {
filters[0].id = LZMA_FILTER_LZMA2;
filters[0].options = nullptr;
filters[1].id = LZMA_VLI_UNKNOWN;
filters[1].options = nullptr;
Q_ASSERT(properties.size() == 1);
unsigned char props[1];
props[0] = properties[0];
result = lzma_properties_decode(&filters[0], nullptr, props, sizeof(props));
if (result != LZMA_OK) {
qCWarning(KArchiveLog) << "lzma_properties_decode returned" << result;
freeFilters(filters);
return false;
}
break;
}
case BCJ: {
filters[0].id = LZMA_FILTER_X86;
filters[0].options = nullptr;
unsigned char props[5] = {0x5d, 0x00, 0x00, 0x08, 0x00};
filters[1].id = LZMA_FILTER_LZMA1;
filters[1].options = nullptr;
result = lzma_properties_decode(&filters[1], nullptr, props, sizeof(props));
if (result != LZMA_OK) {
qCWarning(KArchiveLog) << "lzma_properties_decode1 returned" << result;
freeFilters(filters);
return false;
}
filters[2].id = LZMA_VLI_UNKNOWN;
filters[2].options = nullptr;
break;
}
case POWERPC:
case IA64:
case ARM:
case ARMTHUMB:
case SPARC:
// qCDebug(KArchiveLog) << "flag" << flag << "props size" << properties.size();
break;
}
if (flag != AUTO) {
result = lzma_raw_decoder(&d->zStream, filters);
if (result != LZMA_OK) {
qCWarning(KArchiveLog) << "lzma_raw_decoder returned" << result;
freeFilters(filters);
return false;
}
}
freeFilters(filters);
} else if (mode == QIODevice::WriteOnly) {
if (flag == AUTO) {
result = lzma_easy_encoder(&d->zStream, LZMA_PRESET_DEFAULT, LZMA_CHECK_CRC32);
} else {
lzma_filter filters[5];
if (flag == LZMA2) {
lzma_options_lzma lzma_opt;
lzma_lzma_preset(&lzma_opt, LZMA_PRESET_DEFAULT);
filters[0].id = LZMA_FILTER_LZMA2;
filters[0].options = &lzma_opt;
filters[1].id = LZMA_VLI_UNKNOWN;
filters[1].options = nullptr;
}
result = lzma_raw_encoder(&d->zStream, filters);
}
if (result != LZMA_OK) {
qCWarning(KArchiveLog) << "lzma_easy_encoder returned" << result;
return false;
}
} else {
// qCWarning(KArchiveLog) << "Unsupported mode " << mode << ". Only QIODevice::ReadOnly and QIODevice::WriteOnly supported";
return false;
}
d->mode = mode;
d->isInitialized = true;
return true;
}
int KXzFilter::mode() const
{
return d->mode;
}
bool KXzFilter::terminate()
{
if (d->mode == QIODevice::ReadOnly || d->mode == QIODevice::WriteOnly) {
lzma_end(&d->zStream);
} else {
// qCWarning(KArchiveLog) << "Unsupported mode " << d->mode << ". Only QIODevice::ReadOnly and QIODevice::WriteOnly supported";
return false;
}
d->isInitialized = false;
return true;
}
void KXzFilter::reset()
{
// qCDebug(KArchiveLog) << "KXzFilter::reset";
// liblzma doesn't have a reset call...
terminate();
init(d->mode);
}
void KXzFilter::setOutBuffer(char *data, uint maxlen)
{
d->zStream.avail_out = maxlen;
d->zStream.next_out = (uint8_t *)data;
}
void KXzFilter::setInBuffer(const char *data, unsigned int size)
{
d->zStream.avail_in = size;
d->zStream.next_in = (uint8_t *)const_cast<char *>(data);
}
int KXzFilter::inBufferAvailable() const
{
return d->zStream.avail_in;
}
int KXzFilter::outBufferAvailable() const
{
return d->zStream.avail_out;
}
KXzFilter::Result KXzFilter::uncompress()
{
// qCDebug(KArchiveLog) << "Calling lzma_code with avail_in=" << inBufferAvailable() << " avail_out =" << outBufferAvailable();
lzma_ret result;
result = lzma_code(&d->zStream, LZMA_RUN);
/*if (result != LZMA_OK) {
qCDebug(KArchiveLog) << "lzma_code returned " << result;
//qCDebug(KArchiveLog) << "KXzFilter::uncompress " << ( result == LZMA_STREAM_END ? KFilterBase::End : KFilterBase::Error );
}*/
switch (result) {
case LZMA_OK:
return KFilterBase::Ok;
case LZMA_STREAM_END:
return KFilterBase::End;
default:
return KFilterBase::Error;
}
}
KXzFilter::Result KXzFilter::compress(bool finish)
{
// qCDebug(KArchiveLog) << "Calling lzma_code with avail_in=" << inBufferAvailable() << " avail_out=" << outBufferAvailable();
lzma_ret result = lzma_code(&d->zStream, finish ? LZMA_FINISH : LZMA_RUN);
switch (result) {
case LZMA_OK:
return KFilterBase::Ok;
break;
case LZMA_STREAM_END:
// qCDebug(KArchiveLog) << " lzma_code returned " << result;
return KFilterBase::End;
break;
default:
// qCDebug(KArchiveLog) << " lzma_code returned " << result;
return KFilterBase::Error;
break;
}
}
#endif /* HAVE_XZ_SUPPORT */
@@ -0,0 +1,69 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2007-2008 Per Øyvind Karlsen <peroyvind@mandriva.org>
Based on kbzip2filter:
SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KXZFILTER_H
#define KXZFILTER_H
#include <config-compression.h>
#if HAVE_XZ_SUPPORT
#include "kfilterbase.h"
/**
* Internal class used by KCompressionDevice
* @internal
*/
class KXzFilter : public KFilterBase
{
public:
KXzFilter();
~KXzFilter() override;
bool init(int) override;
enum Flag {
AUTO = 0,
LZMA = 1,
LZMA2 = 2,
BCJ = 3, // X86
POWERPC = 4,
IA64 = 5,
ARM = 6,
ARMTHUMB = 7,
SPARC = 8,
};
virtual bool init(int, Flag flag, const QList<unsigned char> &props);
int mode() const override;
bool terminate() override;
void reset() override;
bool readHeader() override
{
return true; // lzma handles it by itself ! Cool !
}
bool writeHeader(const QByteArray &) override
{
return true;
}
void setOutBuffer(char *data, uint maxlen) override;
void setInBuffer(const char *data, uint size) override;
int inBufferAvailable() const override;
int outBufferAvailable() const override;
Result uncompress() override;
Result compress(bool finish) override;
private:
class Private;
Private *const d;
};
#endif
#endif // KXZFILTER_H
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,175 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2002 Holger Schroeder <holger-kde@holgis.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KZIP_H
#define KZIP_H
#include <karchive.h>
#include "kzipfileentry.h" // for source compat
class KZipFileEntry;
/**
* @class KZip zip.h KZip
*
* A class for reading / writing zip archives.
*
* You can use it in QIODevice::ReadOnly or in QIODevice::WriteOnly mode, and it
* behaves just as expected.
* It can also be used in QIODevice::ReadWrite mode, in this case one can
* append files to an existing zip archive. When you append new files, which
* are not yet in the zip, it works as expected, i.e. the files are appended at the end.
* When you append a file, which is already in the file, the reference to the
* old file is dropped and the new one is added to the zip - but the
* old data from the file itself is not deleted, it is still in the
* zipfile. So when you want to have a small and garbage-free zipfile,
* just read the contents of the appended zip file and write it to a new one
* in QIODevice::WriteOnly mode. This is especially important when you don't want
* to leak information of how intermediate versions of files in the zip
* were looking.
*
* For more information on the zip fileformat go to
* http://www.pkware.com/products/enterprise/white_papers/appnote.html
* @author Holger Schroeder <holger-kde@holgis.net>
*/
class KARCHIVE_EXPORT KZip : public KArchive
{
Q_DECLARE_TR_FUNCTIONS(KZip)
public:
/**
* Creates an instance that operates on the given filename.
* using the compression filter associated to given mimetype.
*
* @param filename is a local path (e.g. "/home/holger/myfile.zip")
*/
explicit KZip(const QString &filename);
/**
* Creates an instance that operates on the given device.
* The device can be compressed (KCompressionDevice) or not (QFile, etc.).
* @warning Do not assume that giving a QFile here will decompress the file,
* in case it's compressed!
* @param dev the device to access
*/
explicit KZip(QIODevice *dev);
/**
* If the zip file is still opened, then it will be
* closed automatically by the destructor.
*/
~KZip() override;
/**
* Describes the contents of the "extra field" for a given file in the Zip archive.
*/
enum ExtraField {
NoExtraField = 0, ///< No extra field
ModificationTime = 1, ///< Modification time ("extended timestamp" header)
DefaultExtraField = 1, // alias of ModificationTime
};
/**
* Call this before writeFile or prepareWriting, to define what the next
* file to be written should have in its extra field.
* @param ef the type of "extra field"
* @see extraField()
*/
void setExtraField(ExtraField ef);
/**
* The current type of "extra field" that will be used for new files.
* @return the current type of "extra field"
* @see setExtraField()
*/
ExtraField extraField() const;
/**
* Describes the compression type for a given file in the Zip archive.
*/
enum Compression {
NoCompression = 0, ///< Uncompressed.
DeflateCompression = 1, ///< Deflate compression method.
};
/**
* Call this before writeFile or prepareWriting, to define whether the next
* files to be written should be compressed or not.
* @param c the new compression mode
* @see compression()
*/
void setCompression(Compression c);
/**
* The current compression mode that will be used for new files.
* @return the current compression mode
* @see setCompression()
*/
Compression compression() const;
protected:
/// Reimplemented from KArchive
bool doWriteSymLink(const QString &name,
const QString &target,
const QString &user,
const QString &group,
mode_t perm,
const QDateTime &atime,
const QDateTime &mtime,
const QDateTime &ctime) override;
/// Reimplemented from KArchive
bool doPrepareWriting(const QString &name,
const QString &user,
const QString &group,
qint64 size,
mode_t perm,
const QDateTime &atime,
const QDateTime &mtime,
const QDateTime &creationTime) override;
/**
* Write data to a file that has been created using prepareWriting().
* @param size the size of the file
* @return true if successful, false otherwise
*/
bool doFinishWriting(qint64 size) override;
/**
* Write data to a file that has been created using prepareWriting().
* @param data a pointer to the data
* @param size the size of the chunk
* @return true if successful, false otherwise
*/
bool doWriteData(const char *data, qint64 size) override;
/**
* Opens the archive for reading.
* Parses the directory listing of the archive
* and creates the KArchiveDirectory/KArchiveFile entries.
* @param mode the mode of the file
*/
bool openArchive(QIODevice::OpenMode mode) override;
/// Closes the archive
bool closeArchive() override;
/// Reimplemented from KArchive
bool doWriteDir(const QString &name,
const QString &user,
const QString &group,
mode_t perm,
const QDateTime &atime,
const QDateTime &mtime,
const QDateTime &ctime) override;
protected:
void virtual_hook(int id, void *data) override;
private:
class KZipPrivate;
KZipPrivate *const d;
};
#endif
@@ -0,0 +1,79 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2002 Holger Schroeder <holger-kde@holgis.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KZIPFILEENTRY_H
#define KZIPFILEENTRY_H
#include "karchive.h"
class KZip;
/**
* @class KZipFileEntry kzipfileentry.h KZipFileEntry
*
* A KZipFileEntry represents a file in a zip archive.
*/
class KARCHIVE_EXPORT KZipFileEntry : public KArchiveFile
{
public:
/**
* Creates a new zip file entry. Do not call this, KZip takes care of it.
*/
KZipFileEntry(KZip *zip,
const QString &name,
int access,
const QDateTime &date,
const QString &user,
const QString &group,
const QString &symlink,
const QString &path,
qint64 start,
qint64 uncompressedSize,
int encoding,
qint64 compressedSize);
/**
* Destructor. Do not call this.
*/
~KZipFileEntry() override;
int encoding() const;
qint64 compressedSize() const;
/// Only used when writing
void setCompressedSize(qint64 compressedSize);
/// Header start: only used when writing
void setHeaderStart(qint64 headerstart);
qint64 headerStart() const;
/// CRC: only used when writing
unsigned long crc32() const;
void setCRC32(unsigned long crc32);
/// Name with complete path - KArchiveFile::name() is the filename only (no path)
const QString &path() const;
/**
* @return the content of this file.
* Call data() with care (only once per file), this data isn't cached.
*/
QByteArray data() const override;
/**
* This method returns a QIODevice to read the file contents.
* This is obviously for reading only.
* Note that the ownership of the device is being transferred to the caller,
* who will have to delete it.
* The returned device auto-opens (in readonly mode), no need to open it.
*/
QIODevice *createDevice() const override;
private:
class KZipFileEntryPrivate;
KZipFileEntryPrivate *const d;
};
#endif
@@ -0,0 +1,134 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2021 Albert Astals Cid <aacid@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kzstdfilter.h"
#include "loggingcategory.h"
#include <QIODevice>
#if HAVE_ZSTD_SUPPORT
extern "C" {
#include <zstd.h>
}
class Q_DECL_HIDDEN KZstdFilter::Private
{
public:
union {
ZSTD_CStream *cStream;
ZSTD_DStream *dStream;
};
int mode;
bool isInitialized = false;
ZSTD_inBuffer inBuffer;
ZSTD_outBuffer outBuffer;
};
KZstdFilter::KZstdFilter()
: d(new Private)
{
}
KZstdFilter::~KZstdFilter()
{
}
bool KZstdFilter::init(int mode)
{
if (d->isInitialized) {
terminate();
}
d->inBuffer.size = 0;
d->inBuffer.pos = 0;
if (mode == QIODevice::ReadOnly) {
d->dStream = ZSTD_createDStream();
} else if (mode == QIODevice::WriteOnly) {
d->cStream = ZSTD_createCStream();
} else {
// qCWarning(KArchiveLog) << "Unsupported mode " << mode << ". Only QIODevice::ReadOnly and QIODevice::WriteOnly supported";
return false;
}
d->mode = mode;
d->isInitialized = true;
return true;
}
int KZstdFilter::mode() const
{
return d->mode;
}
bool KZstdFilter::terminate()
{
if (d->mode == QIODevice::ReadOnly) {
ZSTD_freeDStream(d->dStream);
} else if (d->mode == QIODevice::WriteOnly) {
ZSTD_freeCStream(d->cStream);
} else {
// qCWarning(KArchiveLog) << "Unsupported mode " << d->mode << ". Only QIODevice::ReadOnly and QIODevice::WriteOnly supported";
return false;
}
d->isInitialized = false;
return true;
}
void KZstdFilter::reset()
{
terminate();
init(d->mode);
}
void KZstdFilter::setOutBuffer(char *data, uint maxlen)
{
d->outBuffer.dst = data;
d->outBuffer.size = maxlen;
d->outBuffer.pos = 0;
}
void KZstdFilter::setInBuffer(const char *data, unsigned int size)
{
d->inBuffer.src = data;
d->inBuffer.size = size;
d->inBuffer.pos = 0;
}
int KZstdFilter::inBufferAvailable() const
{
return d->inBuffer.size - d->inBuffer.pos;
}
int KZstdFilter::outBufferAvailable() const
{
return d->outBuffer.size - d->outBuffer.pos;
}
KZstdFilter::Result KZstdFilter::uncompress()
{
// qCDebug(KArchiveLog) << "Calling ZSTD_decompressStream with avail_in=" << inBufferAvailable() << " avail_out=" << outBufferAvailable();
const size_t result = ZSTD_decompressStream(d->dStream, &d->outBuffer, &d->inBuffer);
if (ZSTD_isError(result)) {
qCWarning(KArchiveLog) << "ZSTD_decompressStream returned" << result << ZSTD_getErrorName(result);
return KFilterBase::Error;
}
return result == 0 ? KFilterBase::End : KFilterBase::Ok;
}
KZstdFilter::Result KZstdFilter::compress(bool finish)
{
// qCDebug(KArchiveLog) << "Calling ZSTD_compressStream2 with avail_in=" << inBufferAvailable() << " avail_out=" << outBufferAvailable();
const size_t result = ZSTD_compressStream2(d->cStream, &d->outBuffer, &d->inBuffer, finish ? ZSTD_e_end : ZSTD_e_continue);
if (ZSTD_isError(result)) {
return KFilterBase::Error;
}
return finish && result == 0 ? KFilterBase::End : KFilterBase::Ok;
}
#endif
@@ -0,0 +1,54 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2021 Albert Astals Cid <aacid@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KZSTDFILTER_H
#define KZSTDFILTER_H
#include <config-compression.h>
#if HAVE_ZSTD_SUPPORT
#include "kfilterbase.h"
#include <memory>
/**
* Internal class used by KCompressionDevice
* @internal
*/
class KZstdFilter : public KFilterBase
{
public:
KZstdFilter();
~KZstdFilter() override;
bool init(int) override;
int mode() const override;
bool terminate() override;
void reset() override;
bool readHeader() override
{
return true;
}
bool writeHeader(const QByteArray &) override
{
return true;
}
void setOutBuffer(char *data, uint maxlen) override;
void setInBuffer(const char *data, uint size) override;
int inBufferAvailable() const override;
int outBufferAvailable() const override;
Result uncompress() override;
Result compress(bool finish) override;
private:
class Private;
const std::unique_ptr<Private> d;
};
#endif
#endif