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:
@@ -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()
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user