Import Upstream version 5.92.0

This commit is contained in:
openKylinBot 2022-05-19 09:49:00 +08:00
commit 01fbafca6a
99 changed files with 15646 additions and 0 deletions

4
.git-blame-ignore-revs Normal file
View File

@ -0,0 +1,4 @@
#clang-format/tidy
02f0505a0f56fd0680582eb565e439117f320b10
c3cce473d8e3dab1d69617c48e76755326a99017
f7d760797b5bb879e666110b3659b9963cb43a3a

27
.gitignore vendored Normal file
View File

@ -0,0 +1,27 @@
# Ignore the following files
*~
*.diff
*.kate-swp
*.kdev4
.kdev_include_paths
*.kdevelop.pcs
*.moc
*.moc.cpp
*.orig
*.user
.*.swp
.swp.*
Doxyfile
Makefile
avail
random_seed
/build*/
CMakeLists.txt.user*
*.unc-backup*
.cmake/
/.clang-format
/compile_commands.json
.clangd
.idea
/cmake-build*
.cache

9
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,9 @@
# SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
# SPDX-License-Identifier: CC0-1.0
include:
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android-qt6.yml

7
.kde-ci.yml Normal file
View File

@ -0,0 +1,7 @@
Dependencies:
- 'on': ['@all']
'require':
'frameworks/extra-cmake-modules': '@same'
Options:
test-before-installing: True

10
AUTHORS Normal file
View File

@ -0,0 +1,10 @@
Maintainers:
Mario Bensi <mbensi@ipsquad.net>
David Faure <faure@kde.org>
Many other contributors, see git log.
For questions about this package, email kde-frameworks-devel@kde.org.
For bug reports, please use http://bugs.kde.org

121
CMakeLists.txt Normal file
View File

@ -0,0 +1,121 @@
cmake_minimum_required(VERSION 3.16)
set(KF_VERSION "5.92.0") # handled by release scripts
project(KArchive VERSION ${KF_VERSION})
include(FeatureSummary)
find_package(ECM 5.92.0 NO_MODULE)
set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
include(KDEInstallDirs)
include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE)
include(KDECMakeSettings)
include(KDEGitCommitHooks)
include(ECMGenerateExportHeader)
set(REQUIRED_QT_VERSION 5.15.2)
find_package(Qt${QT_MAJOR_VERSION}Core ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE)
find_package(ZLIB)
set_package_properties(ZLIB PROPERTIES
URL "http://www.zlib.net"
DESCRIPTION "Support for gzip compressed files and data streams"
TYPE REQUIRED
PURPOSE "Required by the core KDE libraries and some critical kioslaves"
)
find_package(BZip2)
set_package_properties(BZip2 PROPERTIES
URL "https://sourceware.org/bzip2/"
DESCRIPTION "Support for BZip2 compressed files and data streams"
TYPE RECOMMENDED
PURPOSE "Support for BZip2 compressed files and data streams"
)
find_package(LibLZMA)
set_package_properties(LibLZMA PROPERTIES
URL "http://tukaani.org/xz/"
DESCRIPTION "Support for xz compressed files and data streams"
PURPOSE "Support for xz compressed files and data streams"
)
find_package(PkgConfig)
if (PkgConfig_FOUND)
pkg_check_modules(LibZstd IMPORTED_TARGET "libzstd")
endif()
add_feature_info(LibZstd LibZstd_FOUND
"Support for zstd compressed files and data streams"
)
include(ECMSetupVersion)
include(ECMGenerateHeaders)
include(ECMQtDeclareLoggingCategory)
include(ECMAddQch)
include(ECMDeprecationSettings)
set(EXCLUDE_DEPRECATED_BEFORE_AND_AT 0 CACHE STRING "Control the range of deprecated API excluded from the build [default=0].")
option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF)
add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)")
ecm_setup_version(PROJECT
VARIABLE_PREFIX KARCHIVE
VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/karchive_version.h"
PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5ArchiveConfigVersion.cmake"
SOVERSION 5)
ecm_set_disabled_deprecation_versions(
QT 5.15.2
)
add_subdirectory(src)
if (BUILD_TESTING)
add_subdirectory(autotests)
add_subdirectory(tests)
endif()
# create a Config.cmake and a ConfigVersion.cmake file and install them
set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5Archive")
if (BUILD_QCH)
ecm_install_qch_export(
TARGETS KF5Archive_QCH
FILE KF5ArchiveQchTargets.cmake
DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
COMPONENT Devel
)
set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KF5ArchiveQchTargets.cmake\")")
endif()
include(CMakePackageConfigHelpers)
configure_package_config_file(
"${CMAKE_CURRENT_SOURCE_DIR}/KF5ArchiveConfig.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/KF5ArchiveConfig.cmake"
INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}
)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/karchive_version.h
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KArchive
COMPONENT Devel)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/KF5ArchiveConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/KF5ArchiveConfigVersion.cmake"
DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
COMPONENT Devel)
install(EXPORT KF5ArchiveTargets
DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
FILE KF5ArchiveTargets.cmake
NAMESPACE KF5::)
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)

23
INSTALL Normal file
View File

@ -0,0 +1,23 @@
Here's how to build this framework:
* First create a convenient folder to build the code in:
mkdir build
cd build
* Next run CMake to create the configuration files to be used in the build:
cmake .. \
-DCMAKE_BUILD_TYPE=debug \
-DCMAKE_INSTALL_PREFIX=/usr/local
Various options can be passed to CMake to control how a project gets built, but these are the most common:
The first line tells CMake where it can find the source code that is to be built.
The second line tells CMake what type of build is required, in this example a debug build that will include useful information for when we are debugging any the software.
The third line tells CMake where to install the software.
* Finally compile and install
make
make install

32
KF5ArchiveConfig.cmake.in Normal file
View File

@ -0,0 +1,32 @@
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
find_dependency(Qt@QT_MAJOR_VERSION@Core @REQUIRED_QT_VERSION@)
set(KArchive_HAVE_ZLIB "@ZLIB_FOUND@")
set(KArchive_HAVE_BZIP2 "@BZIP2_FOUND@")
set(KArchive_HAVE_LZMA "@LIBLZMA_FOUND@")
set(KArchive_HAVE_ZSTD "@LibZstd_FOUND@")
if (NOT @BUILD_SHARED_LIBS@)
if (@ZLIB_FOUND@)
find_dependency(ZLIB)
endif()
if (@BZIP2_FOUND@)
find_dependency(BZip2)
endif()
if (@LIBLZMA_FOUND@)
find_dependency(LibLZMA)
endif()
if (@LibZstd_FOUND@)
find_package(PkgConfig)
pkg_check_modules(LibZstd IMPORTED_TARGET "libzstd")
endif()
endif()
include("${CMAKE_CURRENT_LIST_DIR}/KF5ArchiveTargets.cmake")
@PACKAGE_INCLUDE_QCHTARGETS@

22
LICENSES/BSD-2-Clause.txt Normal file
View File

@ -0,0 +1,22 @@
Copyright (c) <year> <owner>. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

121
LICENSES/CC0-1.0.txt Normal file
View File

@ -0,0 +1,121 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.

View File

@ -0,0 +1,446 @@
GNU LIBRARY GENERAL PUBLIC LICENSE
Version 2, June 1991 Copyright (C) 1991 Free Software Foundation, Inc.
51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
Everyone is permitted to copy and distribute verbatim copies of this license
document, but changing it is not allowed.
[This is the first released version of the library GPL. It is numbered 2 because
it goes with version 2 of the ordinary GPL.]
Preamble
The licenses for most software are designed to take away your freedom to share
and change it. By contrast, the GNU General Public Licenses are intended to
guarantee your freedom to share and change free software--to make sure the
software is free for all its users.
This license, the Library General Public License, applies to some specially
designated Free Software Foundation software, and to any other libraries whose
authors decide to use it. You can use it for your libraries, too.
When we speak of free software, we are referring to freedom, not price. Our
General Public Licenses are designed to make sure that you have the freedom
to distribute copies of free software (and charge for this service if you
wish), that you receive source code or can get it if you want it, that you
can change the software or use pieces of it in new free programs; and that
you know you can do these things.
To protect your rights, we need to make restrictions that forbid anyone to
deny you these rights or to ask you to surrender the rights. These restrictions
translate to certain responsibilities for you if you distribute copies of
the library, or if you modify it.
For example, if you distribute copies of the library, whether gratis or for
a fee, you must give the recipients all the rights that we gave you. You must
make sure that they, too, receive or can get the source code. If you link
a program with the library, you must provide complete object files to the
recipients so that they can relink them with the library, after making changes
to the library and recompiling it. And you must show them these terms so they
know their rights.
Our method of protecting your rights has two steps: (1) copyright the library,
and (2) offer you this license which gives you legal permission to copy, distribute
and/or modify the library.
Also, for each distributor's protection, we want to make certain that everyone
understands that there is no warranty for this free library. If the library
is modified by someone else and passed on, we want its recipients to know
that what they have is not the original version, so that any problems introduced
by others will not reflect on the original authors' reputations.
Finally, any free program is threatened constantly by software patents. We
wish to avoid the danger that companies distributing free software will individually
obtain patent licenses, thus in effect transforming the program into proprietary
software. To prevent this, we have made it clear that any patent must be licensed
for everyone's free use or not licensed at all.
Most GNU software, including some libraries, is covered by the ordinary GNU
General Public License, which was designed for utility programs. This license,
the GNU Library General Public License, applies to certain designated libraries.
This license is quite different from the ordinary one; be sure to read it
in full, and don't assume that anything in it is the same as in the ordinary
license.
The reason we have a separate public license for some libraries is that they
blur the distinction we usually make between modifying or adding to a program
and simply using it. Linking a program with a library, without changing the
library, is in some sense simply using the library, and is analogous to running
a utility program or application program. However, in a textual and legal
sense, the linked executable is a combined work, a derivative of the original
library, and the ordinary General Public License treats it as such.
Because of this blurred distinction, using the ordinary General Public License
for libraries did not effectively promote software sharing, because most developers
did not use the libraries. We concluded that weaker conditions might promote
sharing better.
However, unrestricted linking of non-free programs would deprive the users
of those programs of all benefit from the free status of the libraries themselves.
This Library General Public License is intended to permit developers of non-free
programs to use free libraries, while preserving your freedom as a user of
such programs to change the free libraries that are incorporated in them.
(We have not seen how to achieve this as regards changes in header files,
but we have achieved it as regards changes in the actual functions of the
Library.) The hope is that this will lead to faster development of free libraries.
The precise terms and conditions for copying, distribution and modification
follow. Pay close attention to the difference between a "work based on the
library" and a "work that uses the library". The former contains code derived
from the library, while the latter only works together with the library.
Note that it is possible for a library to be covered by the ordinary General
Public License rather than by this special one.
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library which contains a
notice placed by the copyright holder or other authorized party saying it
may be distributed under the terms of this Library General Public License
(also called "this License"). Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data prepared
so as to be conveniently linked with application programs (which use some
of those functions and data) to form executables.
The "Library", below, refers to any such software library or work which has
been distributed under these terms. A "work based on the Library" means either
the Library or any derivative work under copyright law: that is to say, a
work containing the Library or a portion of it, either verbatim or with modifications
and/or translated straightforwardly into another language. (Hereinafter, translation
is included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for making modifications
to it. For a library, complete source code means all the source code for all
modules it contains, plus any associated interface definition files, plus
the scripts used to control compilation and installation of the library.
Activities other than copying, distribution and modification are not covered
by this License; they are outside its scope. The act of running a program
using the Library is not restricted, and output from such a program is covered
only if its contents constitute a work based on the Library (independent of
the use of the Library in a tool for writing it). Whether that is true depends
on what the Library does and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's complete source
code as you receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice and disclaimer
of warranty; keep intact all the notices that refer to this License and to
the absence of any warranty; and distribute a copy of this License along with
the Library.
You may charge a fee for the physical act of transferring a copy, and you
may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Library or any portion of it,
thus forming a work based on the Library, and copy and distribute such modifications
or work under the terms of Section 1 above, provided that you also meet all
of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices stating that
you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no charge to all
third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a table of
data to be supplied by an application program that uses the facility, other
than as an argument passed when the facility is invoked, then you must make
a good faith effort to ensure that, in the event an application does not supply
such function or table, the facility still operates, and performs whatever
part of its purpose remains meaningful.
(For example, a function in a library to compute square roots has a purpose
that is entirely well-defined independent of the application. Therefore, Subsection
2d requires that any application-supplied function or table used by this function
must be optional: if the application does not supply it, the square root function
must still compute square roots.)
These requirements apply to the modified work as a whole. If identifiable
sections of that work are not derived from the Library, and can be reasonably
considered independent and separate works in themselves, then this License,
and its terms, do not apply to those sections when you distribute them as
separate works. But when you distribute the same sections as part of a whole
which is a work based on the Library, the distribution of the whole must be
on the terms of this License, whose permissions for other licensees extend
to the entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest your
rights to work written entirely by you; rather, the intent is to exercise
the right to control the distribution of derivative or collective works based
on the Library.
In addition, mere aggregation of another work not based on the Library with
the Library (or with a work based on the Library) on a volume of a storage
or distribution medium does not bring the other work under the scope of this
License.
3. You may opt to apply the terms of the ordinary GNU General Public License
instead of this License to a given copy of the Library. To do this, you must
alter all the notices that refer to this License, so that they refer to the
ordinary GNU General Public License, version 2, instead of to this License.
(If a newer version than version 2 of the ordinary GNU General Public License
has appeared, then you can specify that version instead if you wish.) Do not
make any other change in these notices.
Once this change is made in a given copy, it is irreversible for that copy,
so the ordinary GNU General Public License applies to all subsequent copies
and derivative works made from that copy.
This option is useful when you wish to copy part of the code of the Library
into a program that is not a library.
4. You may copy and distribute the Library (or a portion or derivative of
it, under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you accompany it with the complete corresponding
machine-readable source code, which must be distributed under the terms of
Sections 1 and 2 above on a medium customarily used for software interchange.
If distribution of object code is made by offering access to copy from a designated
place, then offering equivalent access to copy the source code from the same
place satisfies the requirement to distribute the source code, even though
third parties are not compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the Library, but
is designed to work with the Library by being compiled or linked with it,
is called a "work that uses the Library". Such a work, in isolation, is not
a derivative work of the Library, and therefore falls outside the scope of
this License.
However, linking a "work that uses the Library" with the Library creates an
executable that is a derivative of the Library (because it contains portions
of the Library), rather than a "work that uses the library". The executable
is therefore covered by this License. Section 6 states terms for distribution
of such executables.
When a "work that uses the Library" uses material from a header file that
is part of the Library, the object code for the work may be a derivative work
of the Library even though the source code is not. Whether this is true is
especially significant if the work can be linked without the Library, or if
the work is itself a library. The threshold for this to be true is not precisely
defined by law.
If such an object file uses only numerical parameters, data structure layouts
and accessors, and small macros and small inline functions (ten lines or less
in length), then the use of the object file is unrestricted, regardless of
whether it is legally a derivative work. (Executables containing this object
code plus portions of the Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may distribute
the object code for the work under the terms of Section 6. Any executables
containing that work also fall under Section 6, whether or not they are linked
directly with the Library itself.
6. As an exception to the Sections above, you may also compile or link a "work
that uses the Library" with the Library to produce a work containing portions
of the Library, and distribute that work under terms of your choice, provided
that the terms permit modification of the work for the customer's own use
and reverse engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the Library
is used in it and that the Library and its use are covered by this License.
You must supply a copy of this License. If the work during execution displays
copyright notices, you must include the copyright notice for the Library among
them, as well as a reference directing the user to the copy of this License.
Also, you must do one of these things:
a) Accompany the work with the complete corresponding machine-readable source
code for the Library including whatever changes were used in the work (which
must be distributed under Sections 1 and 2 above); and, if the work is an
executable linked with the Library, with the complete machine-readable "work
that uses the Library", as object code and/or source code, so that the user
can modify the Library and then relink to produce a modified executable containing
the modified Library. (It is understood that the user who changes the contents
of definitions files in the Library will not necessarily be able to recompile
the application to use the modified definitions.)
b) Accompany the work with a written offer, valid for at least three years,
to give the same user the materials specified in Subsection 6a, above, for
a charge no more than the cost of performing this distribution.
c) If distribution of the work is made by offering access to copy from a designated
place, offer equivalent access to copy the above specified materials from
the same place.
d) Verify that the user has already received a copy of these materials or
that you have already sent this user a copy.
For an executable, the required form of the "work that uses the Library" must
include any data and utility programs needed for reproducing the executable
from it. However, as a special exception, the source code distributed need
not include anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the operating
system on which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license restrictions of
other proprietary libraries that do not normally accompany the operating system.
Such a contradiction means you cannot use both them and the Library together
in an executable that you distribute.
7. You may place library facilities that are a work based on the Library side-by-side
in a single library together with other library facilities not covered by
this License, and distribute such a combined library, provided that the separate
distribution of the work based on the Library and of the other library facilities
is otherwise permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work based on the
Library, uncombined with any other library facilities. This must be distributed
under the terms of the Sections above.
b) Give prominent notice with the combined library of the fact that part of
it is a work based on the Library, and explaining where to find the accompanying
uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute the Library
except as expressly provided under this License. Any attempt otherwise to
copy, modify, sublicense, link with, or distribute the Library is void, and
will automatically terminate your rights under this License. However, parties
who have received copies, or rights, from you under this License will not
have their licenses terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not signed
it. However, nothing else grants you permission to modify or distribute the
Library or its derivative works. These actions are prohibited by law if you
do not accept this License. Therefore, by modifying or distributing the Library
(or any work based on the Library), you indicate your acceptance of this License
to do so, and all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the Library),
the recipient automatically receives a license from the original licensor
to copy, distribute, link with or modify the Library subject to these terms
and conditions. You may not impose any further restrictions on the recipients'
exercise of the rights granted herein. You are not responsible for enforcing
compliance by third parties to this License.
11. If, as a consequence of a court judgment or allegation of patent infringement
or for any other reason (not limited to patent issues), conditions are imposed
on you (whether by court order, agreement or otherwise) that contradict the
conditions of this License, they do not excuse you from the conditions of
this License. If you cannot distribute so as to satisfy simultaneously your
obligations under this License and any other pertinent obligations, then as
a consequence you may not distribute the Library at all. For example, if a
patent license would not permit royalty-free redistribution of the Library
by all those who receive copies directly or indirectly through you, then the
only way you could satisfy both it and this License would be to refrain entirely
from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any patents
or other property right claims or to contest validity of any such claims;
this section has the sole purpose of protecting the integrity of the free
software distribution system which is implemented by public license practices.
Many people have made generous contributions to the wide range of software
distributed through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing to
distribute software through any other system and a licensee cannot impose
that choice.
This section is intended to make thoroughly clear what is believed to be a
consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in certain
countries either by patents or by copyrighted interfaces, the original copyright
holder who places the Library under this License may add an explicit geographical
distribution limitation excluding those countries, so that distribution is
permitted only in or among countries not thus excluded. In such case, this
License incorporates the limitation as if written in the body of this License.
13. The Free Software Foundation may publish revised and/or new versions of
the Library General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to address
new problems or concerns.
Each version is given a distinguishing version number. If the Library specifies
a version number of this License which applies to it and "any later version",
you have the option of following the terms and conditions either of that version
or of any later version published by the Free Software Foundation. If the
Library does not specify a license version number, you may choose any version
ever published by the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free programs
whose distribution conditions are incompatible with these, write to the author
to ask for permission. For software which is copyrighted by the Free Software
Foundation, write to the Free Software Foundation; we sometimes make exceptions
for this. Our decision will be guided by the two goals of preserving the free
status of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE
STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY
"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE
THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE
OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA
OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES
OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH
HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest possible
use to the public, we recommend making it free software that everyone can
redistribute and change. You can do so by permitting redistribution under
these terms (or, alternatively, under the terms of the ordinary General Public
License).
To apply these terms, attach the following notices to the library. It is safest
to attach them to the start of each source file to most effectively convey
the exclusion of warranty; and each file should have at least the "copyright"
line and a pointer to where the full notice is found.
one line to give the library's name and an idea of what it does.
Copyright (C) year name of author
This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Library General Public License as published by the Free
Software Foundation; either version 2 of the License, or (at your option)
any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more
details.
You should have received a copy of the GNU Library General Public License
along with this library; if not, write to the Free Software Foundation, Inc.,
51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your school,
if any, to sign a "copyright disclaimer" for the library, if necessary. Here
is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in
the library `Frob' (a library for tweaking knobs) written
by James Random Hacker.
signature of Ty Coon, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!

22
README.md Normal file
View File

@ -0,0 +1,22 @@
# KArchive
Reading, creating, and manipulating file archives
## Introduction
KArchive provides classes for easy reading, creation and manipulation of
"archive" formats like ZIP and TAR.
It also provides transparent compression and decompression of data, like the
GZip format, via a subclass of QIODevice.
## Usage
If you want to read and write compressed data, just create an instance of
KCompressionDevice and write to or read from that.
If you want to read and write archive formats, create an instance of the
appropriate subclass of KArchive (eg: K7Zip for 7-Zip files). You may need to
combine this with usage of KCompressionDevice (see the API documentation for the
relevant KArchive subclass for details).

78
autotests/CMakeLists.txt Normal file
View File

@ -0,0 +1,78 @@
remove_definitions(-DQT_NO_CAST_FROM_ASCII)
include(ECMAddTests)
find_package(Qt${QT_MAJOR_VERSION}Test ${REQUIRED_QT_VERSION} CONFIG QUIET)
find_package(Qt${QT_MAJOR_VERSION}Network)
if(NOT Qt${QT_MAJOR_VERSION}Test_FOUND)
message(STATUS "Qt${QT_MAJOR_VERSION}Test not found, autotests will not be built.")
return()
endif()
ecm_add_tests(
karchivetest.cpp
kfiltertest.cpp
deprecatedtest.cpp
LINK_LIBRARIES KF5::Archive Qt${QT_MAJOR_VERSION}::Test
)
target_link_libraries(kfiltertest ZLIB::ZLIB)
########### klimitediodevicetest ###############
ecm_add_test(
klimitediodevicetest.cpp
../src/klimitediodevice.cpp
TEST_NAME klimitediodevicetest
LINK_LIBRARIES Qt${QT_MAJOR_VERSION}::Test
)
target_include_directories(klimitediodevicetest
PRIVATE $<TARGET_PROPERTY:KF5Archive,INTERFACE_INCLUDE_DIRECTORIES>)
########## kcompressiondevicetest ##############
if (Qt${QT_MAJOR_VERSION}Network_FOUND)
ecm_add_test(
kcompressiondevicetest.cpp
LINK_LIBRARIES KF5::Archive Qt${QT_MAJOR_VERSION}::Test Qt${QT_MAJOR_VERSION}::Network
)
set(testDir $<TARGET_FILE_DIR:kcompressiondevicetest>)
get_filename_component(topdir ${CMAKE_CURRENT_SOURCE_DIR}/.. ABSOLUTE)
add_custom_command(TARGET kcompressiondevicetest POST_BUILD
COMMAND ${CMAKE_COMMAND} -E tar czf
${testDir}/kcompressiondevice_test.tar.gz examples
WORKING_DIRECTORY ${topdir})
add_custom_command(TARGET kcompressiondevicetest POST_BUILD
COMMAND ${CMAKE_COMMAND} -E tar cjf
${testDir}/kcompressiondevice_test.tar.bz2 examples
WORKING_DIRECTORY ${topdir})
if (LIBLZMA_FOUND)
add_custom_command(TARGET kcompressiondevicetest POST_BUILD
COMMAND ${CMAKE_COMMAND} -E tar cJf
${testDir}/kcompressiondevice_test.tar.xz examples
WORKING_DIRECTORY ${topdir})
endif()
if (LibZstd_FOUND)
# cmake doesn't support creating zstd files so run tar directly
# which is a bit annoying since not all tars support zstd either
# so we first check that the installed tar has the --zstd
# option and then we check that the zstd binary is available
execute_process(COMMAND tar --help OUTPUT_VARIABLE TAR_OUTPUT)
if (TAR_OUTPUT MATCHES ".*--zstd.*")
find_program(ZSTD_FOUND zstd)
if (ZSTD_FOUND)
add_custom_command(TARGET kcompressiondevicetest POST_BUILD
COMMAND tar --zstd -cf
${testDir}/kcompressiondevice_test.tar.zst examples
WORKING_DIRECTORY ${topdir})
target_compile_definitions(kcompressiondevicetest PRIVATE HAVE_ZSTD_SUPPORT_FILE)
endif()
endif()
endif()
endif()

BIN
autotests/data/artest.a Normal file

Binary file not shown.

Binary file not shown.

BIN
autotests/data/out.epub Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
autotests/data/twofiles.gz Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,59 @@
/* This file is part of the KDE project
SPDX-FileCopyrightText: 2006, 2010 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2012 Mario Bensi <mbensi@ipsquad.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include <kzip.h>
#include <QTest>
static const char s_zipFileName[] = "deprecatedtest.zip";
class DeprecatedTest : public QObject
{
Q_OBJECT
#if KARCHIVE_ENABLE_DEPRECATED_SINCE(5, 0)
private Q_SLOTS:
void testKArchiveWriteFile()
{
KZip zip(s_zipFileName);
QVERIFY(zip.open(QIODevice::WriteOnly));
const QByteArray fileData("There could be a fire, if there is smoke.");
const QString fileName = QStringLiteral("wisdom");
QVERIFY(zip.writeFile(fileName, "konqi", "dragons", fileData.constData(), fileData.size()));
QVERIFY(zip.close());
QVERIFY(zip.open(QIODevice::ReadOnly));
const KArchiveDirectory *dir = zip.directory();
QVERIFY(dir != nullptr);
const QStringList listing = dir->entries();
QCOMPARE(listing.count(), 1);
QCOMPARE(listing.at(0), fileName);
const KArchiveEntry *entry = dir->entry(listing.at(0));
QCOMPARE(entry->permissions(), mode_t(0100644));
QVERIFY(!entry->isDirectory());
const KArchiveFile *fileEntry = static_cast<const KArchiveFile *>(entry);
QCOMPARE(fileEntry->size(), fileData.size());
QCOMPARE(fileEntry->data(), fileData);
}
/**
* @see QTest::cleanupTestCase()
*/
void cleanupTestCase()
{
QFile::remove(s_zipFileName);
}
#endif
};
QTEST_MAIN(DeprecatedTest)
#include <deprecatedtest.moc>

Binary file not shown.

1542
autotests/karchivetest.cpp Normal file

File diff suppressed because it is too large Load Diff

128
autotests/karchivetest.h Normal file
View File

@ -0,0 +1,128 @@
/* This file is part of the KDE project
SPDX-FileCopyrightText: 2006 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2012 Mario Bensi <mbensi@ipsquad.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KARCHIVETEST_H
#define KARCHIVETEST_H
#include <QObject>
#include <config-compression.h>
class KArchiveTest : public QObject
{
Q_OBJECT
void setupData();
void setup7ZipData();
private Q_SLOTS:
void initTestCase();
void testEmptyFilename();
void testNullDevice();
void testNonExistentFile();
void testCreateTar_data();
void testCreateTar();
void testCreateTarXXX_data()
{
setupData();
}
void testCreateTarXXX();
void testReadTar_data()
{
setupData();
}
void testReadTar();
void testUncompress_data()
{
setupData();
}
void testUncompress();
void testTarFileData_data()
{
setupData();
}
void testTarFileData();
void testTarCopyTo_data()
{
setupData();
}
void testTarCopyTo();
void testTarReadWrite_data()
{
setupData();
}
void testTarReadWrite();
void testTarMaxLength_data();
void testTarMaxLength();
void testTarGlobalHeader();
void testTarPrefix();
void testTarDirectoryForgotten();
void testTarEmptyFileMissingDir();
void testTarRootDir();
void testTarDirectoryTwice();
void testTarIgnoreRelativePathOutsideArchive();
void testTarLongNonASCIINames();
void testTarShortNonASCIINames();
void testCreateZip();
void testCreateZipError();
void testReadZipError();
void testReadZip();
void testZipFileData();
void testZipCopyTo();
void testZipMaxLength();
void testZipWithNonLatinFileNames();
void testZipWithOverwrittenFileName();
void testZipAddLocalDirectory();
void testZipReadRedundantDataDescriptor_data();
void testZipReadRedundantDataDescriptor();
void testZipDirectoryPermissions();
void testZipUnusualButValid();
void testZipDuplicateNames();
void testZipWithinZip();
void testRcc();
void testAr();
#if HAVE_XZ_SUPPORT
void testCreate7Zip_data()
{
setup7ZipData();
}
void testCreate7Zip();
void testRead7Zip_data()
{
setup7ZipData();
}
void testRead7Zip();
void test7ZipFileData_data()
{
setup7ZipData();
}
void test7ZipFileData();
void test7ZipCopyTo_data()
{
setup7ZipData();
}
void test7ZipCopyTo();
void test7ZipReadWrite_data()
{
setup7ZipData();
}
void test7ZipReadWrite();
void test7ZipMaxLength_data()
{
setup7ZipData();
}
void test7ZipMaxLength();
#endif
void cleanupTestCase();
};
#endif

View File

@ -0,0 +1,223 @@
/* This file is part of the KDE project
SPDX-FileCopyrightText: 2015 Luiz Romário Santana Rios <luizromario@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kcompressiondevicetest.h"
#include "kcompressiondevice_p.h"
#include <config-compression.h>
#include <QBuffer>
#include <QDir>
#include <QDirIterator>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QTemporaryDir>
#include <QTest>
#include <QVector>
QTEST_MAIN(KCompressionDeviceTest)
static QString archiveFileName(const QString &extension)
{
return QFINDTESTDATA(QString("kcompressiondevice_test.%1").arg(extension));
}
QNetworkReply *KCompressionDeviceTest::getArchive(const QString &extension)
{
const QString kcompressionTest = archiveFileName(extension);
QNetworkReply *r = qnam.get(QNetworkRequest(QUrl::fromLocalFile(kcompressionTest)));
QEventLoop l;
connect(&qnam, &QNetworkAccessManager::finished, &l, &QEventLoop::quit);
l.exec();
return r;
}
QString KCompressionDeviceTest::formatExtension(KCompressionDevice::CompressionType type) const
{
switch (type) {
case KCompressionDevice::GZip:
return "tar.gz";
case KCompressionDevice::BZip2:
return "tar.bz2";
case KCompressionDevice::Xz:
return "tar.xz";
case KCompressionDevice::Zstd:
return "tar.zst";
case KCompressionDevice::None:
return QString();
}
return QString(); // silence compiler warning
}
void KCompressionDeviceTest::setDeviceToArchive(QIODevice *d, KCompressionDevice::CompressionType type)
{
KCompressionDevice *devRawPtr = new KCompressionDevice(d, true, type);
archive.reset(new KTar(devRawPtr));
device.reset(devRawPtr);
}
void KCompressionDeviceTest::testBufferedDevice(KCompressionDevice::CompressionType type)
{
QNetworkReply *r = getArchive(formatExtension(type));
const QByteArray data = r->readAll();
QVERIFY(!data.isEmpty());
const int expectedSize = QFileInfo(archiveFileName(formatExtension(type))).size();
QVERIFY(expectedSize > 0);
QCOMPARE(data.size(), expectedSize);
QBuffer *b = new QBuffer;
b->setData(data);
setDeviceToArchive(b, type);
testExtraction();
}
void KCompressionDeviceTest::testExtraction()
{
QTemporaryDir temp;
QString oldCurrentDir = QDir::currentPath();
QDir::setCurrent(temp.path());
QVERIFY(archive->open(QIODevice::ReadOnly));
QVERIFY(archive->directory()->copyTo("."));
QVERIFY(QDir("examples").exists());
QVERIFY(QDir("examples/bzip2gzip").exists());
QVERIFY(QDir("examples/helloworld").exists());
QVERIFY(QDir("examples/tarlocalfiles").exists());
QVERIFY(QDir("examples/unzipper").exists());
const QStringList fileList = {QStringLiteral("examples/bzip2gzip/CMakeLists.txt"),
QStringLiteral("examples/bzip2gzip/main.cpp"),
QStringLiteral("examples/helloworld/CMakeLists.txt"),
QStringLiteral("examples/helloworld/helloworld.pro"),
QStringLiteral("examples/helloworld/main.cpp"),
QStringLiteral("examples/tarlocalfiles/CMakeLists.txt"),
QStringLiteral("examples/tarlocalfiles/main.cpp"),
QStringLiteral("examples/unzipper/CMakeLists.txt"),
QStringLiteral("examples/unzipper/main.cpp")};
for (const QString &s : fileList) {
QFileInfo extractedFile(s);
QFileInfo sourceFile(QFINDTESTDATA("../" + s));
QVERIFY(extractedFile.exists());
QCOMPARE(extractedFile.size(), sourceFile.size());
}
QDir::setCurrent(oldCurrentDir);
}
void KCompressionDeviceTest::regularKTarUsage()
{
archive.reset(new KTar(QFINDTESTDATA("kcompressiondevice_test.tar.gz")));
device.reset();
testExtraction();
}
void KCompressionDeviceTest::testGZipBufferedDevice()
{
testBufferedDevice(KCompressionDevice::GZip);
}
void KCompressionDeviceTest::testBZip2BufferedDevice()
{
#if HAVE_BZIP2_SUPPORT
testBufferedDevice(KCompressionDevice::BZip2);
#else
QSKIP("This test needs bzip2 support");
#endif
}
void KCompressionDeviceTest::testXzBufferedDevice()
{
#if HAVE_XZ_SUPPORT
testBufferedDevice(KCompressionDevice::Xz);
#else
QSKIP("This test needs xz support");
#endif
}
void KCompressionDeviceTest::testZstdBufferedDevice()
{
#ifdef HAVE_ZSTD_SUPPORT_FILE
testBufferedDevice(KCompressionDevice::Zstd);
#else
QSKIP("This test needs zstd support");
#endif
}
void KCompressionDeviceTest::testWriteErrorOnOpen()
{
// GIVEN
QString fileName("/I/dont/exist/kcompressiondevicetest-write.gz");
KCompressionDevice dev(fileName, KCompressionDevice::GZip);
// WHEN
QVERIFY(!dev.open(QIODevice::WriteOnly));
// THEN
QCOMPARE(dev.error(), QFileDevice::OpenError);
#ifdef Q_OS_WIN
QCOMPARE(dev.errorString(), QStringLiteral("The system cannot find the path specified."));
#else
QCOMPARE(dev.errorString(), QStringLiteral("No such file or directory"));
#endif
}
void KCompressionDeviceTest::testWriteErrorOnClose()
{
// GIVEN
QFile file("kcompressiondevicetest-write.gz");
KCompressionDevice dev(&file, false, KCompressionDevice::GZip);
QVERIFY(dev.open(QIODevice::WriteOnly));
const QByteArray data = "Hello world";
QCOMPARE(dev.write(data), data.size());
// This is nasty, it's just a way to try and trigger an error on flush, without filling up a partition first ;)
file.close();
QVERIFY(file.open(QIODevice::ReadOnly));
QTest::ignoreMessage(QtWarningMsg, "QIODevice::write (QFile, \"kcompressiondevicetest-write.gz\"): ReadOnly device");
// WHEN
dev.close(); // I want a QVERIFY here... https://bugreports.qt.io/browse/QTBUG-70033
// THEN
QCOMPARE(int(dev.error()), int(QFileDevice::WriteError));
}
void KCompressionDeviceTest::testSeekReadUncompressedBuffer_data()
{
QTest::addColumn<int>("dataSize");
QTest::addColumn<int>("realDataPos");
QTest::newRow("1.5buffer") << BUFFER_SIZE + BUFFER_SIZE / 2 << BUFFER_SIZE;
QTest::newRow("5seekbuffer") << 5 * SEEK_BUFFER_SIZE << 4 * SEEK_BUFFER_SIZE;
}
void KCompressionDeviceTest::testSeekReadUncompressedBuffer()
{
QFETCH(int, dataSize);
QFETCH(int, realDataPos);
QByteArray ba(dataSize, 0);
// all data is zero except after realDataPos that it's 0 to 9
for (int i = 0; i < 10; ++i) {
ba[realDataPos + i] = i;
}
QBuffer b;
b.setData(ba);
QVERIFY(b.open(QIODevice::ReadOnly));
KCompressionDevice kcd(&b, false, KCompressionDevice::GZip);
QVERIFY(kcd.open(QIODevice::ReadOnly));
QVERIFY(kcd.seek(realDataPos));
// the 10 bytes after realDataPos should be 0 to 9
const QByteArray kcdData = kcd.read(10);
QCOMPARE(kcdData.size(), 10);
for (int i = 0; i < kcdData.size(); ++i) {
QCOMPARE(kcdData[i], i);
}
}

View File

@ -0,0 +1,51 @@
/* This file is part of the KDE project
SPDX-FileCopyrightText: 2015 Luiz Romário Santana Rios <luizromario@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCOMPRESSIONDEVICETEST_H
#define KCOMPRESSIONDEVICETEST_H
#include <QObject>
#include <QNetworkAccessManager>
#include <QScopedPointer>
#include <KCompressionDevice>
#include <KTar>
class QNetworkReply;
class KCompressionDeviceTest : public QObject
{
Q_OBJECT
private:
QNetworkReply *getArchive(const QString &extension);
QString formatExtension(KCompressionDevice::CompressionType type) const;
void setDeviceToArchive(QIODevice *d, KCompressionDevice::CompressionType type);
void testBufferedDevice(KCompressionDevice::CompressionType type);
void testExtraction();
QNetworkAccessManager qnam;
QScopedPointer<KCompressionDevice> device;
QScopedPointer<KTar> archive;
private Q_SLOTS:
void regularKTarUsage();
void testGZipBufferedDevice();
void testBZip2BufferedDevice();
void testXzBufferedDevice();
void testZstdBufferedDevice();
void testWriteErrorOnOpen();
void testWriteErrorOnClose();
void testSeekReadUncompressedBuffer_data();
void testSeekReadUncompressedBuffer();
};
#endif

478
autotests/kfiltertest.cpp Normal file
View File

@ -0,0 +1,478 @@
/*
* SPDX-FileCopyrightText: 2002-2005 David Faure <faure@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kfiltertest.h"
#include <QBuffer>
#include <QTest>
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#include <QTextCodec>
#endif
#include <QRandomGenerator>
#include <QSaveFile>
#include "kcompressiondevice.h"
#include "kfilterbase.h"
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QTextStream>
#include <config-compression.h>
#include <zlib.h>
#ifdef Q_OS_UNIX
#include <limits.h>
#include <unistd.h>
#endif
QTEST_MAIN(KFilterTest)
void KFilterTest::initTestCase()
{
qRegisterMetaType<KCompressionDevice::CompressionType>();
const QString currentdir = QDir::currentPath();
pathgz = currentdir + "/test.gz";
pathbz2 = currentdir + "/test.bz2";
pathxz = currentdir + "/test.xz";
pathnone = currentdir + "/test.txt";
pathzstd = currentdir + "/test.zst";
// warning, update the COMPAREs in test_block_write() if changing the test data...
testData = "hello world\n";
}
void KFilterTest::test_block_write(const QString &fileName, const QByteArray &data)
{
KCompressionDevice dev(fileName);
bool ok = dev.open(QIODevice::WriteOnly);
QVERIFY(ok);
const int ret = dev.write(data);
QCOMPARE(ret, data.size());
dev.close();
QVERIFY(QFile::exists(fileName));
}
void KFilterTest::test_block_write()
{
qDebug() << " -- test_block_write gzip -- ";
test_block_write(pathgz, testData);
QCOMPARE(QFileInfo(pathgz).size(), 33LL); // size of test.gz
#if HAVE_BZIP2_SUPPORT
qDebug() << " -- test_block_write bzip2 -- ";
test_block_write(pathbz2, testData);
QCOMPARE(QFileInfo(pathbz2).size(), 52LL); // size of test.bz2
#endif
#if HAVE_XZ_SUPPORT
qDebug() << " -- test_block_write xz -- ";
test_block_write(pathxz, testData);
QCOMPARE(QFileInfo(pathxz).size(), 64LL); // size of test.lzma
#endif
qDebug() << " -- test_block_write none -- ";
test_block_write(pathnone, testData);
QCOMPARE(QFileInfo(pathnone).size(), 12LL); // size of test.txt
#if HAVE_ZSTD_SUPPORT
qDebug() << " -- test_block_write zstd -- ";
test_block_write(pathzstd, testData);
QCOMPARE(QFileInfo(pathzstd).size(), 24LL); // size of test.zst
#endif
}
void KFilterTest::test_biggerWrites()
{
const QString currentdir = QDir::currentPath();
const QString outFile = currentdir + "/test_big.gz";
// Find the out-of-bounds from #157706/#188415
QByteArray data;
data.reserve(10000);
auto *generator = QRandomGenerator::global();
// Prepare test data
for (int i = 0; i < 8170; ++i) {
data.append((char)(generator->bounded(256)));
}
QCOMPARE(data.size(), 8170);
// 8170 random bytes compress to 8194 bytes due to the gzip header/footer.
// Now we can go one by one until we pass 8192.
// On 32 bit systems it crashed with data.size()=8173, before the "no room for footer yet" fix.
int compressedSize = 0;
while (compressedSize < 8200) {
test_block_write(outFile, data);
compressedSize = QFileInfo(outFile).size();
qDebug() << data.size() << "compressed into" << compressedSize;
// Test data is valid
test_readall(outFile, QString::fromLatin1("application/gzip"), data);
data.append((char)(generator->bounded(256)));
}
}
void KFilterTest::test_block_read(const QString &fileName)
{
KCompressionDevice dev(fileName);
bool ok = dev.open(QIODevice::ReadOnly);
QVERIFY(ok);
QByteArray array(1024, '\0');
QByteArray read;
int n;
while ((n = dev.read(array.data(), array.size()))) {
QVERIFY(n > 0);
read += QByteArray(array.constData(), n);
// qDebug() << "read returned " << n;
// qDebug() << "read='" << read << "'";
// pos() has no real meaning on sequential devices
// Ah, but kzip uses kfilterdev as a non-sequential device...
QCOMPARE((int)dev.pos(), (int)read.size());
// qDebug() << "dev.at = " << dev->at();
}
QCOMPARE(read, testData);
// Test seeking back
ok = dev.seek(0);
// test readAll
read = dev.readAll();
QCOMPARE(read.size(), testData.size());
QCOMPARE(read, testData);
dev.close();
}
void KFilterTest::test_block_read()
{
qDebug() << " -- test_block_read gzip -- ";
test_block_read(pathgz);
#if HAVE_BZIP2_SUPPORT
qDebug() << " -- test_block_read bzip2 -- ";
test_block_read(pathbz2);
#endif
#if HAVE_XZ_SUPPORT
qDebug() << " -- test_block_read lzma -- ";
test_block_read(pathxz);
#endif
qDebug() << " -- test_block_read none -- ";
test_block_read(pathnone);
#if HAVE_ZSTD_SUPPORT
qDebug() << " -- test_block_read zstd -- ";
test_block_read(pathzstd);
#endif
}
void KFilterTest::test_getch(const QString &fileName)
{
KCompressionDevice dev(fileName);
bool ok = dev.open(QIODevice::ReadOnly);
QVERIFY(ok);
QByteArray read;
char ch;
while (dev.getChar(&ch)) {
// printf("%c",ch);
read += ch;
}
dev.close();
QCOMPARE(read, testData);
}
void KFilterTest::test_getch()
{
qDebug() << " -- test_getch gzip -- ";
test_getch(pathgz);
#if HAVE_BZIP2_SUPPORT
qDebug() << " -- test_getch bzip2 -- ";
test_getch(pathbz2);
#endif
#if HAVE_XZ_SUPPORT
qDebug() << " -- test_getch lzma -- ";
test_getch(pathxz);
#endif
qDebug() << " -- test_getch none -- ";
test_getch(pathnone);
#if HAVE_ZSTD_SUPPORT
qDebug() << " -- test_getch zstd -- ";
test_getch(pathzstd);
#endif
}
void KFilterTest::test_textstream(const QString &fileName)
{
KCompressionDevice dev(fileName);
bool ok = dev.open(QIODevice::ReadOnly);
QVERIFY(ok);
QTextStream ts(&dev);
QString readStr = ts.readAll();
dev.close();
QByteArray read = readStr.toLatin1();
QCOMPARE(read, testData);
}
void KFilterTest::test_textstream()
{
qDebug() << " -- test_textstream gzip -- ";
test_textstream(pathgz);
#if HAVE_BZIP2_SUPPORT
qDebug() << " -- test_textstream bzip2 -- ";
test_textstream(pathbz2);
#endif
#if HAVE_XZ_SUPPORT
qDebug() << " -- test_textstream lzma -- ";
test_textstream(pathxz);
#endif
qDebug() << " -- test_textstream none -- ";
test_textstream(pathnone);
#if HAVE_ZSTD_SUPPORT
qDebug() << " -- test_textstream zstd -- ";
test_textstream(pathzstd);
#endif
}
void KFilterTest::test_readall(const QString &fileName, const QString &mimeType, const QByteArray &expectedData)
{
QFile file(fileName);
KCompressionDevice::CompressionType type = KCompressionDevice::compressionTypeForMimeType(mimeType);
KCompressionDevice flt(&file, false, type);
bool ok = flt.open(QIODevice::ReadOnly);
QVERIFY(ok);
const QByteArray read = flt.readAll();
QCOMPARE(read.size(), expectedData.size());
QCOMPARE(read, expectedData);
// Now using QBuffer
file.seek(0);
QByteArray compressedData = file.readAll();
QVERIFY(!compressedData.isEmpty());
QBuffer buffer(&compressedData);
KCompressionDevice device(&buffer, false, type);
QVERIFY(device.open(QIODevice::ReadOnly));
QCOMPARE(device.readAll(), expectedData);
}
void KFilterTest::test_readall()
{
qDebug() << " -- test_readall gzip -- ";
test_readall(pathgz, QString::fromLatin1("application/gzip"), testData);
#if HAVE_BZIP2_SUPPORT
qDebug() << " -- test_readall bzip2 -- ";
test_readall(pathbz2, QString::fromLatin1("application/x-bzip"), testData);
#endif
#if HAVE_XZ_SUPPORT
qDebug() << " -- test_readall lzma -- ";
test_readall(pathxz, QString::fromLatin1("application/x-xz"), testData);
#endif
qDebug() << " -- test_readall gzip-derived -- ";
test_readall(pathgz, QString::fromLatin1("image/svg+xml-compressed"), testData);
qDebug() << " -- test_readall none -- ";
test_readall(pathnone, QString::fromLatin1("text/plain"), testData);
#if HAVE_ZSTD_SUPPORT
qDebug() << " -- test_readall zstd -- ";
test_readall(pathzstd, QString::fromLatin1("application/zstd"), testData);
#endif
}
void KFilterTest::test_uncompressed()
{
// Can KCompressionDevice handle uncompressed data even when using gzip decompression?
qDebug() << " -- test_uncompressed -- ";
QBuffer buffer(&testData);
buffer.open(QIODevice::ReadOnly);
KCompressionDevice::CompressionType type = KCompressionDevice::compressionTypeForMimeType(QString::fromLatin1("application/gzip"));
KCompressionDevice flt(&buffer, false, type);
bool ok = flt.open(QIODevice::ReadOnly);
QVERIFY(ok);
QByteArray read = flt.readAll();
QCOMPARE(read.size(), testData.size());
QCOMPARE(read, testData);
}
void KFilterTest::test_findFilterByMimeType_data()
{
QTest::addColumn<QString>("mimeType");
QTest::addColumn<KCompressionDevice::CompressionType>("type");
// direct mimetype name
QTest::newRow("application/gzip") << QString::fromLatin1("application/gzip") << KCompressionDevice::GZip;
#if HAVE_BZIP2_SUPPORT
QTest::newRow("application/x-bzip") << QString::fromLatin1("application/x-bzip") << KCompressionDevice::BZip2;
QTest::newRow("application/x-bzip2") << QString::fromLatin1("application/x-bzip2") << KCompressionDevice::BZip2;
#else
QTest::newRow("application/x-bzip") << QString::fromLatin1("application/x-bzip") << KCompressionDevice::None;
QTest::newRow("application/x-bzip2") << QString::fromLatin1("application/x-bzip2") << KCompressionDevice::None;
#endif
// indirect compressed mimetypes
QTest::newRow("application/x-gzdvi") << QString::fromLatin1("application/x-gzdvi") << KCompressionDevice::GZip;
// non-compressed mimetypes
QTest::newRow("text/plain") << QString::fromLatin1("text/plain") << KCompressionDevice::None;
QTest::newRow("application/x-tar") << QString::fromLatin1("application/x-tar") << KCompressionDevice::None;
}
void KFilterTest::test_findFilterByMimeType()
{
QFETCH(QString, mimeType);
QFETCH(KCompressionDevice::CompressionType, type);
KCompressionDevice::CompressionType compressionType = KCompressionDevice::compressionTypeForMimeType(mimeType);
QCOMPARE(compressionType, type);
}
static void getCompressedData(QByteArray &data, QByteArray &compressedData)
{
data = "Hello world, this is a test for deflate, from bug 114830 / 117683";
compressedData.resize(long(data.size() * 1.1f) + 12L); // requirements of zlib::compress2
unsigned long out_bufferlen = compressedData.size();
const int ret = compress2((Bytef *)compressedData.data(), &out_bufferlen, (const Bytef *)data.constData(), data.size(), 1);
QCOMPARE(ret, Z_OK);
compressedData.resize(out_bufferlen);
}
void KFilterTest::test_deflateWithZlibHeader()
{
QByteArray data;
QByteArray deflatedData;
getCompressedData(data, deflatedData);
#if 0 // Can't use KFilterDev for this, we need to call KGzipFilter::init(QIODevice::ReadOnly, KGzipFilter::ZlibHeader);
QBuffer buffer(&deflatedData);
QIODevice *flt = KFilterDev::device(&buffer, "application/gzip", false);
static_cast<KFilterDev *>(flt)->setSkipHeaders();
bool ok = flt->open(QIODevice::ReadOnly);
QVERIFY(ok);
const QByteArray read = flt->readAll();
#else
// Copied from HTTPFilter (which isn't linked into any kdelibs library)
KFilterBase *mFilterDevice = KCompressionDevice::filterForCompressionType(KCompressionDevice::GZip);
mFilterDevice->setFilterFlags(KFilterBase::ZlibHeaders);
mFilterDevice->init(QIODevice::ReadOnly);
mFilterDevice->setInBuffer(deflatedData.constData(), deflatedData.size());
char buf[8192];
mFilterDevice->setOutBuffer(buf, sizeof(buf));
KFilterBase::Result result = mFilterDevice->uncompress();
QCOMPARE(result, KFilterBase::End);
const int bytesOut = sizeof(buf) - mFilterDevice->outBufferAvailable();
QVERIFY(bytesOut);
QByteArray read(buf, bytesOut);
mFilterDevice->terminate();
delete mFilterDevice;
#endif
QCOMPARE(QString::fromLatin1(read.constData()), QString::fromLatin1(data.constData())); // more readable output than the line below
QCOMPARE(read, data);
// For the same test with HTTPFilter: see httpfiltertest.cpp
}
void KFilterTest::test_pushData() // ### UNFINISHED
{
// HTTPFilter says KFilterDev doesn't support the case where compressed data
// is arriving in chunks. Let's test that.
QFile file(pathgz);
QVERIFY(file.open(QIODevice::ReadOnly));
const QByteArray compressed = file.readAll();
const int firstChunkSize = compressed.size() / 2;
QByteArray firstData(compressed.constData(), firstChunkSize);
QBuffer inBuffer(&firstData);
QVERIFY(inBuffer.open(QIODevice::ReadWrite));
KCompressionDevice::CompressionType type = KCompressionDevice::compressionTypeForMimeType(QString::fromLatin1("application/gzip"));
KCompressionDevice flt(&inBuffer, false, type);
QVERIFY(flt.open(QIODevice::ReadOnly));
QByteArray read = flt.readAll();
qDebug() << QString::fromLatin1(read.constData());
// And later...
inBuffer.write(QByteArray(compressed.data() + firstChunkSize, compressed.size() - firstChunkSize));
QCOMPARE(inBuffer.data().size(), compressed.size());
read += flt.readAll();
qDebug() << QString::fromLatin1(read.constData());
// ### indeed, doesn't work currently. So we use HTTPFilter instead, for now.
}
void KFilterTest::test_saveFile_data()
{
QTest::addColumn<QString>("fileName");
QTest::addColumn<KCompressionDevice::CompressionType>("compressionType");
QTest::newRow("gz") << "test_saveFile.gz" << KCompressionDevice::GZip;
QTest::newRow("none") << "test_saveFile" << KCompressionDevice::None;
}
void KFilterTest::test_saveFile()
{
QFETCH(QString, fileName);
QFETCH(KCompressionDevice::CompressionType, compressionType);
int numLines = 1000;
const QString lineTemplate = QStringLiteral("Hello world, this is the text for line %1");
const QString currentdir = QDir::currentPath();
const QString outFile = QDir::currentPath() + '/' + fileName;
{
QSaveFile file(outFile);
file.setDirectWriteFallback(true);
QVERIFY(file.open(QIODevice::WriteOnly));
KCompressionDevice device(&file, false, compressionType);
QVERIFY(device.open(QIODevice::WriteOnly));
QTextStream stream(&device);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
stream.setCodec(QTextCodec::codecForName("UTF-8"));
#endif
for (int i = 0; i < numLines; ++i) {
stream << lineTemplate.arg(i);
stream << QString("\n");
}
stream.flush();
QCOMPARE(stream.status(), QTextStream::Ok);
// device.write("The data to be compressed");
device.close();
QVERIFY(file.commit());
}
QVERIFY(QFile::exists(outFile));
KCompressionDevice reader(outFile, compressionType);
QVERIFY(reader.open(QIODevice::ReadOnly));
QString expectedFullData;
for (int i = 0; i < numLines; ++i) {
QCOMPARE(QString::fromUtf8(reader.readLine()), QString(lineTemplate.arg(i) + '\n'));
expectedFullData += QString(lineTemplate.arg(i) + '\n');
}
KCompressionDevice otherReader(outFile);
QVERIFY(otherReader.open(QIODevice::ReadOnly));
QCOMPARE(QString::fromLatin1(otherReader.readAll()), expectedFullData);
QVERIFY(otherReader.atEnd());
}
void KFilterTest::test_twofilesgztogether()
{
// Reported as 232843
// twofiles generated with
// echo foo > foo; echo bar > bar ; gzip -c foo > twofiles.gz; gzip -c bar >> twofiles.gz
// as documented in the gzip manpage
QString data = QFINDTESTDATA("data/twofiles.gz");
KCompressionDevice dev(data);
QVERIFY(dev.open(QIODevice::ReadOnly));
QByteArray extractedData = dev.readAll();
QByteArray expectedData{"foo\nbar\n"};
QCOMPARE(extractedData, expectedData);
}
void KFilterTest::test_threefilesgztogether()
{
// Generated similarly to the one above
// This catches the case where there's more than two streams available in the same buffer fed to KGzipFilter
QString data = QFINDTESTDATA("data/threefiles.gz");
KCompressionDevice dev(data);
QVERIFY(dev.open(QIODevice::ReadOnly));
QByteArray extractedData = dev.readAll();
QByteArray expectedData{"foo\nbar\nbaz\n"};
QCOMPARE(extractedData, expectedData);
}

49
autotests/kfiltertest.h Normal file
View File

@ -0,0 +1,49 @@
/*
* SPDX-FileCopyrightText: 2002-2005 David Faure <faure@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KFILTERTEST_H
#define KFILTERTEST_H
#include <QObject>
class KFilterTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void test_block_write();
void test_block_read();
void test_biggerWrites();
void test_getch();
void test_textstream();
void test_readall();
void test_uncompressed();
void test_findFilterByMimeType_data();
void test_findFilterByMimeType();
void test_deflateWithZlibHeader();
void test_pushData();
void test_saveFile_data();
void test_saveFile();
void test_twofilesgztogether();
void test_threefilesgztogether();
private:
void test_block_write(const QString &fileName, const QByteArray &data);
void test_block_read(const QString &fileName);
void test_getch(const QString &fileName);
void test_textstream(const QString &fileName);
void test_readall(const QString &fileName, const QString &mimeType, const QByteArray &expectedData);
private:
QString pathgz;
QString pathbz2;
QString pathxz;
QString pathnone;
QString pathzstd;
QByteArray testData;
};
#endif

View File

@ -0,0 +1,64 @@
/* This file is part of the KDE project
SPDX-FileCopyrightText: 2009 Pino Toscano <pino@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "klimitediodevicetest.h"
#include "klimitediodevice_p.h"
#include <QTest>
QTEST_MAIN(KLimitedIODeviceTest)
void KLimitedIODeviceTest::addChunk(const QByteArray &chunk)
{
ChunkData cd;
cd.data = chunk;
cd.offset = m_chunks.isEmpty() ? 0 : m_chunks.last().offset + m_chunks.last().data.size();
m_chunks.append(cd);
m_data.append(chunk);
}
void KLimitedIODeviceTest::initTestCase()
{
addChunk("Test of string");
addChunk("second part of the large buffer");
addChunk("... which will be used to test the KLimitedIODevice");
m_buffer.setBuffer(&m_data);
m_buffer.open(QIODevice::ReadOnly);
}
void KLimitedIODeviceTest::testReadChunks_data()
{
QTest::addColumn<int>("index");
for (int i = 0; i < m_chunks.count(); ++i) {
const ChunkData &d = m_chunks.at(i);
QTest::newRow(d.data.constData()) << i;
}
}
void KLimitedIODeviceTest::testReadChunks()
{
QFETCH(int, index);
const ChunkData &chunk = m_chunks.at(index);
KLimitedIODevice dev(&m_buffer, chunk.offset, chunk.data.size());
QVERIFY(dev.isOpen());
QCOMPARE(dev.readAll(), chunk.data);
}
void KLimitedIODeviceTest::testSeeking()
{
const ChunkData &chunk = m_chunks.at(2);
KLimitedIODevice dev(&m_buffer, chunk.offset, chunk.data.size());
QVERIFY(dev.seek(dev.size() - 16));
QCOMPARE(dev.readAll(), chunk.data.right(16));
QVERIFY(dev.seek(0));
QCOMPARE(dev.readAll(), chunk.data);
}

View File

@ -0,0 +1,38 @@
/* This file is part of the KDE project
SPDX-FileCopyrightText: 2009 Pino Toscano <pino@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KLIMITEDIODEVICETEST_H
#define KLIMITEDIODEVICETEST_H
#include <QBuffer>
#include <QByteArray>
#include <QList>
#include <QObject>
struct ChunkData {
QByteArray data;
int offset;
};
class KLimitedIODeviceTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void testReadChunks_data();
void testReadChunks();
void testSeeking();
private:
void addChunk(const QByteArray &chunk);
QByteArray m_data;
QBuffer m_buffer;
QList<ChunkData> m_chunks;
};
#endif

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

31
conanfile.py Normal file
View File

@ -0,0 +1,31 @@
from conans import ConanFile, CMake, tools
class KArchiveConan(ConanFile):
name = "KArchive"
version = "5.37.0"
license = "LGPL-2.1"
url = "https://api.kde.org/frameworks/karchive/html/index.html"
settings = "os", "compiler", "build_type", "arch"
# build this as shared library by default, but static builds are an option
options = {"shared": [True, False]}
default_options = "shared=True"
generators = "cmake"
exports_sources = "*"
def build(self):
cmake = CMake(self)
# change the library install dir to just "lib" as that's what Conan expects in its packages
args = ['-DCMAKE_INSTALL_PREFIX="%s"' % self.package_folder,
'-DKDE_INSTALL_LIBDIR=lib']
self.run('cmake %s %s %s' % (self.source_folder, cmake.command_line, " ".join(args)))
self.run("cmake --build . --target install %s" % cmake.build_config)
def package(self):
# ideally nothing here, cmake with install takes care of it
pass
def package_info(self):
self.cpp_info.libs = ["KF5Archive"]
self.cpp_info.includedirs = ['include/KF5', 'include/KF5/KArchive']

7
docs/Doxyfile.local Normal file
View File

@ -0,0 +1,7 @@
### KApiDox Project-specific Overrides File
# define so that deprecated API is not skipped
PREDEFINED += \
"KARCHIVE_ENABLE_DEPRECATED_SINCE(x, y)=1" \
"KARCHIVE_BUILD_DEPRECATED_SINCE(x, y)=1" \
"KARCHIVE_DEPRECATED_VERSION(x, y, t)="

View File

@ -0,0 +1,6 @@
project(BZip2GZip)
find_package(KF5Archive ${KF_VERSION} REQUIRED)
add_executable(bzip2gzip main.cpp)
target_link_libraries(bzip2gzip KF5::Archive)

View File

@ -0,0 +1,72 @@
/* This file is part of the KDE project
SPDX-FileCopyrightText: 2014 Maarten De Meyer <de.meyer.maarten@gmail.com>
SPDX-License-Identifier: BSD-2-Clause
*/
/*
* bzip2gzip
* This example shows the usage of KCompressionDevice.
* It converts BZip2 files to GZip archives.
*
* api: KCompressionDevice(QIODevice * inputDevice, bool autoDeleteInputDevice, CompressionType type)
* api: KCompressionDevice(const QString & fileName, CompressionType type)
* api: QIODevice::readAll()
* api: QIODevice::read(qint64 maxSize)
* api: QIODevice::write(const QByteArray &data)
*
* Usage: ./bzip2gzip <archive.bz2>
*/
#include <QCoreApplication>
#include <QFile>
#include <QFileInfo>
#include <QStringList>
#include <KCompressionDevice>
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QStringList args(app.arguments());
if (args.size() != 2) {
qWarning("Usage: ./bzip2gzip <archive.bz2>");
return 1;
}
QString inputFile = args.at(1);
QFile file(inputFile);
QFileInfo info(inputFile);
if (info.suffix() != QLatin1String("bz2")) {
qCritical("Error: not a valid BZip2 file!");
return 1;
}
//@@snippet_begin(kcompressiondevice_example)
// Open the input archive
KCompressionDevice input(&file, false, KCompressionDevice::BZip2);
input.open(QIODevice::ReadOnly);
QString outputFile = (info.completeBaseName() + QLatin1String(".gz"));
// Open the new output file
KCompressionDevice output(outputFile, KCompressionDevice::GZip);
output.open(QIODevice::WriteOnly);
while (!input.atEnd()) {
// Read and uncompress the data
QByteArray data = input.read(512);
// Write data like you would to any other QIODevice
output.write(data);
}
input.close();
output.close();
//@@snippet_end
return 0;
}

View File

@ -0,0 +1,16 @@
# For more information see the CMake documentation:
# http://www.cmake.org/cmake/help/documentation.html
# http://techbase.kde.org/Development/Tutorials/CMake
# Project name
project(HelloWorld)
# Look for the KArchive module
find_package(KF5Archive ${KF_VERSION} REQUIRED)
find_package(Qt${QT_MAJOR_VERSION}Core REQUIRED)
add_executable(helloworld main.cpp)
# Link our executable with the KArchive library
target_link_libraries(helloworld KF5::Archive Qt${QT_MAJOR_VERSION}::Core)

View File

@ -0,0 +1,6 @@
# Show how to use KArchive using qmake
# Set $QMAKEPATH to your KArchive install prefix if this is not found.
QT += KArchive
SOURCES += main.cpp

View File

@ -0,0 +1,64 @@
/* This file is part of the KDE project
SPDX-FileCopyrightText: 2013 Maarten De Meyer <de.meyer.maarten@gmail.com>
SPDX-License-Identifier: BSD-2-Clause
*/
/*
* HelloWorld
*
* Example to show very basic usage of KArchive with CMake
*
* Usage:
* mkdir build && cd build
* cmake ..
* make
* ./helloworld
*/
#include <QDebug>
#include <kzip.h>
int main()
{
//@@snippet_begin(helloworld)
// Create a zip archive
KZip archive(QStringLiteral("hello.zip"));
// Open our archive for writing
if (archive.open(QIODevice::WriteOnly)) {
// The archive is open, we can now write data
archive.writeFile(QStringLiteral("world"), // File name
QByteArray("The whole world inside a hello."), // Data
0100644, // Permissions
QStringLiteral("owner"), // Owner
QStringLiteral("users")); // Group
// Don't forget to close!
archive.close();
}
if (archive.open(QIODevice::ReadOnly)) {
const KArchiveDirectory *dir = archive.directory();
const KArchiveEntry *e = dir->entry("world");
if (!e) {
qDebug() << "File not found!";
return -1;
}
const KArchiveFile *f = static_cast<const KArchiveFile *>(e);
QByteArray arr(f->data());
qDebug() << arr; // the file contents
// To avoid reading everything into memory in one go, we can use createDevice() instead
QIODevice *dev = f->createDevice();
while (!dev->atEnd()) {
qDebug() << dev->readLine();
}
delete dev;
}
//@@snippet_end
return 0;
}

View File

@ -0,0 +1,6 @@
project(TarLocalFiles)
find_package(KF5Archive ${KF_VERSION} REQUIRED)
add_executable(tarlocalfiles main.cpp)
target_link_libraries(tarlocalfiles KF5::Archive)

View File

@ -0,0 +1,59 @@
/* This file is part of the KDE project
SPDX-FileCopyrightText: 2013 Maarten De Meyer <de.meyer.maarten@gmail.com>
SPDX-License-Identifier: BSD-2-Clause
*/
/*
* TarLocalFiles
* This example shows how to add local files and directories to a KArchive
*
* api: addLocalFile(fileName, destName)
* api: addLocalDirectory(dirName, destName)
*
* Usage: ./tarlocalfiles <file-1> <file-n>
*/
#include <QCoreApplication>
#include <QDir>
#include <QFileInfo>
#include <ktar.h>
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QStringList files(app.arguments());
// Create or open an archive
KTar archive(QStringLiteral("myFiles.tar.gz"));
// Prepare the archive for writing.
if (!archive.open(QIODevice::WriteOnly)) {
// Failed to open file.
return 1;
}
if (files.size() <= 1) {
// No files given.
qWarning("Usage: ./tarlocalfiles <file>");
return 1;
}
for (int i = 1; i < files.size(); ++i) {
QFileInfo localFileOrDir(files.at(i));
if (localFileOrDir.isFile()) {
QString name = localFileOrDir.fileName();
archive.addLocalFile(name, name);
} else if (localFileOrDir.isDir()) {
QString name = QDir(files.at(i)).dirName();
// Add this folder and all its contents
archive.addLocalDirectory(name, name);
}
}
archive.close();
return 0;
}

View File

@ -0,0 +1,6 @@
project(Unzipper)
find_package(KF5Archive ${KF_VERSION} REQUIRED)
add_executable(unzipper main.cpp)
target_link_libraries(unzipper KF5::Archive)

View File

@ -0,0 +1,56 @@
/* This file is part of the KDE project
SPDX-FileCopyrightText: 2014 Maarten De Meyer <de.meyer.maarten@gmail.com>
SPDX-License-Identifier: BSD-2-Clause
*/
/*
* Unzipper
* This example shows how to extract all files from a zip archive.
*
* api: KArchive::directory()
* api: KArchiveDirectory::copyTo(QString destination, bool recursive)
*
* Usage: ./unzipper <archive>
*/
#include <QCoreApplication>
#include <QDir>
#include <kzip.h>
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QStringList args(app.arguments());
if (args.size() != 2) {
// Too many or too few arguments
qWarning("Usage: ./unzipper <archive.zip>");
return 1;
}
QString file = args.at(1);
KZip archive(file);
// Open the archive
if (!archive.open(QIODevice::ReadOnly)) {
qWarning("Cannot open " + file.toLatin1());
qWarning("Is it a valid zip file?");
return 1;
}
// Take the root folder from the archive and create a KArchiveDirectory object.
// KArchiveDirectory represents a directory in a KArchive.
const KArchiveDirectory *root = archive.directory();
// We can extract all contents from a KArchiveDirectory to a destination.
// recursive true will also extract subdirectories.
QString destination = QDir::currentPath();
bool recursive = true;
root->copyTo(destination, recursive);
archive.close();
return 0;
}

21
metainfo.yaml Normal file
View File

@ -0,0 +1,21 @@
maintainer: dfaure
description: File compression
tier: 1
type: functional
platforms:
- name: Linux
- name: FreeBSD
- name: Windows
- name: macOS
- name: Android
portingAid: false
deprecated: false
release: true
libraries:
- qmake: KArchive
cmake: "KF5::Archive"
cmakename: KF5Archive
public_lib: true
group: Frameworks
subgroup: Tier 1

155
src/CMakeLists.txt Normal file
View File

@ -0,0 +1,155 @@
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)
set(deprecated_HEADER_NAMES)
add_library(KF5Archive)
add_library(KF5::Archive ALIAS KF5Archive)
if(BZIP2_FOUND)
target_sources(KF5Archive PRIVATE kbzip2filter.cpp)
target_link_libraries(KF5Archive PRIVATE BZip2::BZip2)
endif()
if(LIBLZMA_FOUND)
target_sources(KF5Archive PRIVATE kxzfilter.cpp k7zip.cpp)
target_link_libraries(KF5Archive PRIVATE LibLZMA::LibLZMA)
endif()
if (LibZstd_FOUND)
target_sources(KF5Archive PRIVATE kzstdfilter.cpp)
target_link_libraries(KF5Archive PRIVATE PkgConfig::LibZstd)
endif()
target_sources(KF5Archive PRIVATE karchive.cpp
kar.cpp
kcompressiondevice.cpp
kfilterbase.cpp
kgzipfilter.cpp
klimitediodevice.cpp
knonefilter.cpp
ktar.cpp
kzip.cpp
krcc.cpp
)
if(NOT EXCLUDE_DEPRECATED_BEFORE_AND_AT STREQUAL "CURRENT" AND
EXCLUDE_DEPRECATED_BEFORE_AND_AT VERSION_LESS 5.85.0)
list(APPEND deprecated_HEADER_NAMES KFilterDev)
target_sources(KF5Archive PRIVATE kfilterdev.cpp)
endif()
ecm_qt_declare_logging_category(KF5Archive
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(KF5Archive
BASE_NAME KArchive
GROUP_BASE_NAME KF
VERSION ${KF_VERSION}
DEPRECATED_BASE_VERSION 0
DEPRECATION_VERSIONS 5.0 5.85
EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT}
)
target_include_directories(KF5Archive
INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/KArchive>"
)
target_link_libraries(KF5Archive
PUBLIC
Qt${QT_MAJOR_VERSION}::Core
PRIVATE
ZLIB::ZLIB
)
set_target_properties(KF5Archive PROPERTIES
VERSION ${KARCHIVE_VERSION}
SOVERSION ${KARCHIVE_SOVERSION}
EXPORT_NAME "Archive"
)
ecm_generate_headers(KArchive_HEADERS
HEADER_NAMES
KArchive
KArchiveEntry
KArchiveFile
KArchiveDirectory
KAr
KCompressionDevice
KFilterBase
KRcc
KTar
KZip
KZipFileEntry
${deprecated_HEADER_NAMES}
REQUIRED_HEADERS KArchive_HEADERS
)
install(TARGETS KF5Archive
EXPORT KF5ArchiveTargets
${KF5_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(
KF5Archive_QCH
NAME KArchive
BASE_NAME KF5Archive
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
Qt5Core_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()
include(ECMGeneratePriFile)
ecm_generate_pri_file(BASE_NAME KArchive LIB_NAME KF5Archive DEPS "core" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF}/KArchive)
install(FILES ${PRI_FILENAME}
DESTINATION ${ECM_MKSPECS_INSTALL_DIR})

View File

@ -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

3034
src/k7zip.cpp Normal file

File diff suppressed because it is too large Load Diff

97
src/k7zip.h Normal file
View File

@ -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 writeData(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

195
src/kar.cpp Normal file
View File

@ -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(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);
}

103
src/kar.h Normal file
View File

@ -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")
*/
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
*/
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

1043
src/karchive.cpp Normal file

File diff suppressed because it is too large Load Diff

436
src/karchive.h Normal file
View File

@ -0,0 +1,436 @@
/* 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 <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().
*/
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.
*/
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());
#if KARCHIVE_ENABLE_DEPRECATED_SINCE(5, 0)
/**
* @deprecated since 5.0, use writeFile(const QString&, const QByteArray&, mode_t, const QString&, const QString&, const QDateTime&, const QDateTime&, const
* QDateTime&)
*/
KARCHIVE_DEPRECATED_VERSION(5,
0,
"Use KArchive::writeFile(const QString&, const QByteArray&, mode_t, const QString&, const QString&, const QDateTime&, const "
"QDateTime&, const QDateTime&)")
bool writeFile(const QString &name,
const QString &user,
const QString &group,
const char *data,
qint64 size,
mode_t perm = 0100644,
const QDateTime &atime = QDateTime(),
const QDateTime &mtime = QDateTime(),
const QDateTime &ctime = QDateTime())
{
QByteArray array(data, size);
return writeFile(name, array, perm, user, group, atime, mtime, ctime);
}
// The above can lead to ambiguous calls when using "..." for the first 4 arguments,
// but that's good, better than unexpected behavior due to the signature change.
#endif
/**
* 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
*/
bool writeFile(const QString &name,
const QByteArray &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
*/
virtual bool writeData(const char *data, qint64 size);
/**
* 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;
/**
* 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

60
src/karchive_p.h Normal file
View File

@ -0,0 +1,60 @@
/* 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)
, rootDir(nullptr)
, saveFile(nullptr)
, dev(nullptr)
, fileName()
, mode(QIODevice::NotOpen)
, deviceOwned(false)
{
}
~KArchivePrivate()
{
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;
KArchiveDirectory *rootDir;
QSaveFile *saveFile;
QIODevice *dev;
QString fileName;
QIODevice::OpenMode mode;
bool deviceOwned; // if true, we (KArchive) own dev and must delete it
QString errorStr{tr("Unknown error")};
};
#endif // KARCHIVE_P_H

127
src/karchivedirectory.h Normal file
View File

@ -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

107
src/karchiveentry.h Normal file
View File

@ -0,0 +1,107 @@
/* 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>
#ifdef Q_OS_WIN
#include <qplatformdefs.h> // mode_t
#endif
class KArchiveDirectory;
class KArchiveFile;
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

109
src/karchivefile.h Normal file
View File

@ -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

184
src/kbzip2filter.cpp Normal file
View File

@ -0,0 +1,184 @@
/* 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) {
qCWarning(KArchiveLog) << "bzDecompress returned" << result;
}
switch (result) {
case BZ_OK:
return KFilterBase::Ok;
case BZ_STREAM_END:
return KFilterBase::End;
default:
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 */

52
src/kbzip2filter.h Normal file
View File

@ -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

515
src/kcompressiondevice.cpp Normal file
View File

@ -0,0 +1,515 @@
/* 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;
#if HAVE_ZSTD_SUPPORT
case KCompressionDevice::Zstd:
return new KZstdFilter;
#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)) {
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;
}

146
src/kcompressiondevice.h Normal file
View File

@ -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
*/
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

View File

@ -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

81
src/kfilterbase.cpp Normal file
View File

@ -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 );*/
}

109
src/kfilterbase.h Normal file
View File

@ -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

21
src/kfilterdev.cpp Normal file
View File

@ -0,0 +1,21 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000, 2006 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kfilterdev.h"
#if KARCHIVE_BUILD_DEPRECATED_SINCE(5, 85)
KFilterDev::KFilterDev(const QString &fileName)
: KCompressionDevice(fileName)
{
}
KCompressionDevice::CompressionType KFilterDev::compressionTypeForMimeType(const QString &mimeType)
{
return KCompressionDevice::compressionTypeForMimeType(mimeType);
}
#endif // KARCHIVE_BUILD_DEPRECATED_SINCE(5, 85)

153
src/kfilterdev.h Normal file
View File

@ -0,0 +1,153 @@
/* 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 __kfilterdev_h
#define __kfilterdev_h
#include <karchive_export.h>
#include <kcompressiondevice.h>
#include <QString>
#if KARCHIVE_ENABLE_DEPRECATED_SINCE(5, 85)
class QFile;
class KFilterBase;
/**
* @class KFilterDev kfilterdev.h KFilterDev
*
* 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).
*
* To simply read/write compressed files, see deviceForFile.
*
* KFilterDev adds MIME type support to KCompressionDevice, and also
* provides compatibility methods for KDE 4 code.
*
* @author David Faure <faure@kde.org>
* @deprecated Since 5.85, use KCompressionDevice directly
*/
class KARCHIVE_EXPORT KFilterDev : public KCompressionDevice
{
Q_OBJECT
public:
/**
* Constructs a KFilterDev for a given FileName.
* @param fileName the name of the file to filter.
* @since 5.0
* @deprecated Since 5.85, use KCompressionDevice(const QString &)
*/
KFilterDev(const QString &fileName);
/**
* Returns the compression type for the given mimetype, if possible. Otherwise returns None.
* This handles simple cases like application/gzip, but also application/x-compressed-tar, and inheritance.
* @deprecated Since 5.85, use KCompressionDevice::compressionTypeForMimeType(const QString &)
*/
KARCHIVE_DEPRECATED_VERSION(5, 85, "Use KCompressionDevice::compressionTypeForMimeType(const QString &)")
static CompressionType compressionTypeForMimeType(const QString &mimetype);
#if KARCHIVE_ENABLE_DEPRECATED_SINCE(5, 0)
/**
* @deprecated Since 5.0, use the constructor instead (if mimetype is empty), or KCompressionDevice (if
* the mimetype is known).
*
* Use:
* KFilterDev dev(fileName)
* instead of:
* QIODevice * dev = KFilterDev::deviceForFile(fileName);
*
* If the mimetype was specified explicitly, use:
* KCompressionDevice dev(fileName, KCompressionDevice::GZip);
* instead of:
* QIODevice * dev = KFilterDev::deviceForFile(fileName, "application/gzip");
*
* Creates an i/o device that is able to read from @p fileName,
* whether it's compressed or not. Available compression filters
* (gzip/bzip2 etc.) will automatically be used.
*
* The compression filter to be used is determined from the @p fileName
* if @p mimetype is empty. Pass "application/gzip" or "application/x-bzip"
* to force the corresponding decompression filter, if available.
*
* Warning: application/x-bzip may not be available.
* In that case a QFile opened on the compressed data will be returned !
* Use KFilterBase::findFilterByMimeType and code similar to what
* deviceForFile is doing, to better control what's happening.
*
* The returned QIODevice has to be deleted after using.
*
* @param fileName the name of the file to filter
* @param mimetype the mime type of the file to filter, or QString() if unknown
* @param forceFilter if true, the function will either find a compression filter, or return 0.
* If false, it will always return a QIODevice. If no
* filter is available it will return a simple QFile.
* This can be useful if the file is usable without a filter.
* @return if a filter has been found, the KCompressionDevice for the filter. If the
* filter does not exist, the return value depends on @p forceFilter.
* The returned KCompressionDevice has to be deleted after using.
*/
KARCHIVE_DEPRECATED_VERSION(5, 0, "See API docs")
static KCompressionDevice *deviceForFile(const QString &fileName, const QString &mimetype = QString(), bool forceFilter = false)
{
KCompressionDevice *device;
if (mimetype.isEmpty()) {
device = new KFilterDev(fileName);
} else {
device = new KCompressionDevice(fileName, compressionTypeForMimeType(mimetype));
}
if (device->compressionType() == KCompressionDevice::None && forceFilter) {
delete device;
return nullptr;
} else {
return device;
}
}
#endif
#if KARCHIVE_ENABLE_DEPRECATED_SINCE(5, 0)
/**
* @deprecated Since 5.0, use KCompressionDevice
*
* Use:
* KCompressionDevice::CompressionType type = KFilterDev::compressionTypeForMimeType(mimeType);
* KCompressionDevice flt(&file, false, type);
* instead of:
* QIODevice *flt = KFilterDev::device(&file, mimeType, false);
*
* Creates an i/o device that is able to read from the QIODevice @p inDevice,
* whether the data is compressed or not. Available compression filters
* (gzip/bzip2 etc.) will automatically be used.
*
* The compression filter to be used is determined @p mimetype .
* Pass "application/gzip" or "application/x-bzip"
* to use the corresponding decompression filter.
*
* Warning: application/x-bzip may not be available.
* In that case 0 will be returned !
*
* The returned QIODevice has to be deleted after using.
* @param inDevice input device. Won't be deleted if @p autoDeleteInDevice = false
* @param mimetype the mime type for the filter
* @param autoDeleteInDevice if true, @p inDevice will be deleted automatically
* @return a KCompressionDevice that filters the original stream. Must be deleted after using
*/
KARCHIVE_DEPRECATED_VERSION(5, 0, "See API docs")
static KCompressionDevice *device(QIODevice *inDevice, const QString &mimetype, bool autoDeleteInDevice = true)
{
if (inDevice == nullptr) {
return nullptr;
}
CompressionType type = compressionTypeForMimeType(mimetype);
KCompressionDevice *device = new KCompressionDevice(inDevice, autoDeleteInDevice, type);
return device;
}
#endif
};
#endif // KARCHIVE_ENABLE_DEPRECATED_SINCE(5, 85)
#endif

366
src/kgzipfilter.cpp Normal file
View File

@ -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 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;
}

59
src/kgzipfilter.h Normal file
View File

@ -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 http://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

71
src/klimitediodevice.cpp Normal file
View File

@ -0,0 +1,71 @@
/* 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();
}

58
src/klimitediodevice_p.h Normal file
View File

@ -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

127
src/knonefilter.cpp Normal file
View File

@ -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;
}
}

48
src/knonefilter.h Normal file
View File

@ -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

162
src/krcc.cpp Normal file
View File

@ -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(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);
}

100
src/krcc.h Normal file
View File

@ -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")
*/
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

964
src/ktar.cpp Normal file
View File

@ -0,0 +1,964 @@
/* 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(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 tar ioslave dramatically,
// if the archive file contains many files.
// This is because the tar ioslave 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 http://pubs.opengroup.org/onlinepubs/009695399/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*/);
// 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;
}
// 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];
memset(buffer, 0, 0x200);
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];
memset(buffer, 0, 0x200);
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];
memset(buffer, 0, 0x200);
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);
}

114
src/ktar.h Normal file
View File

@ -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

279
src/kxzfilter.cpp Normal file
View File

@ -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)
{
QVector<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 QVector<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 */

69
src/kxzfilter.h Normal file
View File

@ -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 QVector<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

1458
src/kzip.cpp Normal file

File diff suppressed because it is too large Load Diff

175
src/kzip.h Normal file
View File

@ -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")
*/
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
*/
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;
/**
* 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 writeData(const char *data, qint64 size) 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 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;
/**
* 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

79
src/kzipfileentry.h Normal file
View File

@ -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

134
src/kzstdfilter.cpp Normal file
View File

@ -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_flush);
if (ZSTD_isError(result)) {
return KFilterBase::Error;
}
return finish && result == 0 ? KFilterBase::End : KFilterBase::Ok;
}
#endif

54
src/kzstdfilter.h Normal file
View File

@ -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

View File

@ -0,0 +1,14 @@
project(PackageTest CXX)
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()
# TODO: rename to Qt6 after branching KF6
find_package(Qt5 5.6 CONFIG REQUIRED
Core
)
# TODO: rename to Qt6 after branching KF6
add_executable(example example.cpp)
target_link_libraries(example ${CONAN_LIBS}
Qt5::Core)

20
test_package/conanfile.py Normal file
View File

@ -0,0 +1,20 @@
from conans import ConanFile, CMake
import os
class KArchiveTestConan(ConanFile):
settings = "os", "compiler", "build_type", "arch"
generators = "cmake"
def build(self):
cmake = CMake(self)
# Current dir is "test_package/build/<build_id>" and CMakeLists.txt is in "test_package"
cmake.configure(source_dir=self.conanfile_directory, build_dir="./")
cmake.build()
def imports(self):
self.copy("*.dll", dst="bin", src="bin")
self.copy("*.dylib*", dst="bin", src="lib")
def test(self):
os.chdir("bin")
self.run(".%sexample" % os.sep)

8
test_package/example.cpp Normal file
View File

@ -0,0 +1,8 @@
#include "kzip.h"
#include <QIODevice>
int main()
{
KZip a("somefile");
return 0;
}

24
tests/CMakeLists.txt Normal file
View File

@ -0,0 +1,24 @@
remove_definitions(-DQT_NO_CAST_FROM_ASCII)
include(ECMMarkAsTest)
macro(KARCHIVE_EXECUTABLE_TESTS)
foreach(_testname ${ARGN})
add_executable(${_testname} ${_testname}.cpp) # TODO NOGUI
target_link_libraries(${_testname} KF5::Archive)
ecm_mark_as_test(${_testname})
endforeach(_testname)
endmacro(KARCHIVE_EXECUTABLE_TESTS)
karchive_executable_tests(
kartest
ktartest
krcctest
kziptest
)
if(LIBLZMA_FOUND)
karchive_executable_tests(
k7ziptest
)
endif()

76
tests/k7ziptest.cpp Normal file
View File

@ -0,0 +1,76 @@
/*
* SPDX-FileCopyrightText: 2011 Mario Bensi <mbensi@ipsquad.net>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "k7zip.h"
#include <QDebug>
#include <stdio.h>
void recursive_print(const KArchiveDirectory *dir, const QString &path)
{
QStringList l = dir->entries();
l.sort();
QStringList::ConstIterator it = l.constBegin();
for (; it != l.constEnd(); ++it) {
const KArchiveEntry *entry = dir->entry((*it));
printf("mode=%07o %s %s %s %s%s %lld isdir=%d\n",
entry->permissions(),
entry->date().toString(QStringLiteral("yyyy-MM-dd hh:mm:ss")).toLatin1().constData(),
entry->user().toLatin1().constData(),
entry->group().toLatin1().constData(),
path.toLatin1().constData(),
(*it).toLatin1().constData(),
entry->isFile() ? static_cast<const KArchiveFile *>(entry)->size() : 0,
entry->isDirectory());
if (!entry->symLinkTarget().isEmpty()) {
printf(" (symlink to %s)\n", qPrintable(entry->symLinkTarget()));
}
if (entry->isDirectory()) {
recursive_print((KArchiveDirectory *)entry, path + (*it) + '/');
}
if (entry->isFile()) {
const KArchiveFile *f = static_cast<const KArchiveFile *>(entry);
QByteArray arr(f->data());
qDebug() << "data" << arr;
QIODevice *dev = f->createDevice();
QByteArray contents = dev->readAll();
qDebug() << "contents" << contents;
delete dev;
}
}
}
// See karchivetest.cpp for the unittest that covers K7Zip.
int main(int argc, char **argv)
{
if (argc != 2) {
printf(
"\n"
" Usage :\n"
" ./k7ziptest /path/to/existing_file.7z tests listing an existing .7z\n");
return 1;
}
K7Zip k7z(argv[1]);
if (!k7z.open(QIODevice::ReadOnly)) {
printf("Could not open %s for reading\n", argv[1]);
return 1;
}
const KArchiveDirectory *dir = k7z.directory();
// printf("calling recursive_print\n");
recursive_print(dir, QLatin1String(""));
// printf("recursive_print called\n");
k7z.close();
return 0;
}

62
tests/kartest.cpp Normal file
View File

@ -0,0 +1,62 @@
/* This file is part of the KDE project
SPDX-FileCopyrightText: 2002-2019 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kar.h"
#include <QDebug>
#include <stdio.h>
void recursive_print(const KArchiveDirectory *dir, const QString &path)
{
QStringList l = dir->entries();
l.sort();
QStringList::ConstIterator it = l.constBegin();
for (; it != l.constEnd(); ++it) {
const KArchiveEntry *entry = dir->entry((*it));
printf("mode=%7o path=%s type=%s size=%lld\n",
entry->permissions(),
qPrintable(path + (*it)),
entry->isFile() ? "file" : "dir",
entry->isFile() ? static_cast<const KArchiveFile *>(entry)->size() : 0);
if (!entry->symLinkTarget().isEmpty()) {
printf(" (symlink to %s)\n", qPrintable(entry->symLinkTarget()));
}
if (entry->isDirectory()) {
recursive_print((KArchiveDirectory *)entry, path + (*it) + '/');
}
}
}
// See karchivetest.cpp for the unittest that covers KAr.
int main(int argc, char **argv)
{
if (argc != 2) {
printf(
"\n"
" Usage :\n"
" ./kartest /path/to/existing_file.a tests listing an existing archive\n");
return 1;
}
KAr archive(argv[1]);
if (!archive.open(QIODevice::ReadOnly)) {
printf("Could not open %s for reading\n", argv[1]);
return 1;
}
const KArchiveDirectory *dir = archive.directory();
// printf("calling recursive_print\n");
recursive_print(dir, QLatin1String(""));
// printf("recursive_print called\n");
archive.close();
return 0;
}

65
tests/krcctest.cpp Normal file
View File

@ -0,0 +1,65 @@
/*
* SPDX-FileCopyrightText: 2002-2014 David Faure <faure@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "krcc.h"
#include <QDebug>
#include <stdio.h>
void recursive_print(const KArchiveDirectory *dir, const QString &path)
{
QStringList l = dir->entries();
l.sort();
QStringList::ConstIterator it = l.constBegin();
for (; it != l.constEnd(); ++it) {
const KArchiveEntry *entry = dir->entry((*it));
printf("mode=%07o %s %s %s%s %lld isdir=%d\n",
entry->permissions(),
entry->user().toLatin1().constData(),
entry->group().toLatin1().constData(),
path.toLatin1().constData(),
(*it).toLatin1().constData(),
entry->isFile() ? static_cast<const KArchiveFile *>(entry)->size() : 0,
entry->isDirectory());
if (!entry->symLinkTarget().isEmpty()) {
printf(" (symlink to %s)\n", qPrintable(entry->symLinkTarget()));
}
if (entry->isDirectory()) {
recursive_print((KArchiveDirectory *)entry, path + (*it) + '/');
}
}
}
// See karchivetest.cpp for the unittest that covers KTar.
int main(int argc, char **argv)
{
if (argc != 2) {
printf(
"\n"
" Usage :\n"
" ./ktartest /path/to/existing_file.tar.gz tests listing an existing tar.gz\n");
return 1;
}
KRcc rcc(argv[1]);
if (!rcc.open(QIODevice::ReadOnly)) {
printf("Could not open %s for reading\n", argv[1]);
return 1;
}
const KArchiveDirectory *dir = rcc.directory();
// printf("calling recursive_print\n");
recursive_print(dir, QLatin1String(""));
// printf("recursive_print called\n");
rcc.close();
return 0;
}

65
tests/ktartest.cpp Normal file
View File

@ -0,0 +1,65 @@
/*
* SPDX-FileCopyrightText: 2002-2005 David Faure <faure@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "ktar.h"
#include <QDebug>
#include <stdio.h>
void recursive_print(const KArchiveDirectory *dir, const QString &path)
{
QStringList l = dir->entries();
l.sort();
QStringList::ConstIterator it = l.constBegin();
for (; it != l.constEnd(); ++it) {
const KArchiveEntry *entry = dir->entry((*it));
printf("mode=%07o %s %s %s%s %lld isdir=%d\n",
entry->permissions(),
entry->user().toLatin1().constData(),
entry->group().toLatin1().constData(),
path.toLatin1().constData(),
(*it).toLatin1().constData(),
entry->isFile() ? static_cast<const KArchiveFile *>(entry)->size() : 0,
entry->isDirectory());
if (!entry->symLinkTarget().isEmpty()) {
printf(" (symlink to %s)\n", qPrintable(entry->symLinkTarget()));
}
if (entry->isDirectory()) {
recursive_print((KArchiveDirectory *)entry, path + (*it) + '/');
}
}
}
// See karchivetest.cpp for the unittest that covers KTar.
int main(int argc, char **argv)
{
if (argc != 2) {
printf(
"\n"
" Usage :\n"
" ./ktartest /path/to/existing_file.tar.gz tests listing an existing tar.gz\n");
return 1;
}
KTar tar(argv[1]);
if (!tar.open(QIODevice::ReadOnly)) {
printf("Could not open %s for reading\n", argv[1]);
return 1;
}
const KArchiveDirectory *dir = tar.directory();
// printf("calling recursive_print\n");
recursive_print(dir, QLatin1String(""));
// printf("recursive_print called\n");
tar.close();
return 0;
}

340
tests/kziptest.cpp Normal file
View File

@ -0,0 +1,340 @@
/*
* SPDX-FileCopyrightText: 2002-2013 David Faure <faure@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kzip.h"
#include "kcompressiondevice.h"
#include <QCoreApplication>
#include <QDebug>
#include <QFile>
#include <stdio.h>
void recursive_print(const KArchiveDirectory *dir, const QString &path)
{
const QStringList lst = dir->entries();
for (const QString &it : lst) {
const KArchiveEntry *entry = dir->entry(it);
printf("mode=%07o %s %s \"%s%s\" size: %lld pos: %lld isdir=%d%s",
entry->permissions(),
entry->user().toLatin1().constData(),
entry->group().toLatin1().constData(),
path.toLatin1().constData(),
it.toLatin1().constData(),
entry->isDirectory() ? 0 : (static_cast<const KArchiveFile *>(entry))->size(),
entry->isDirectory() ? 0 : (static_cast<const KArchiveFile *>(entry))->position(),
entry->isDirectory(),
entry->symLinkTarget().isEmpty() ? "" : QStringLiteral(" symlink: %1").arg(entry->symLinkTarget()).toLatin1().constData());
// if (!entry->isDirectory()) printf("%d", (static_cast<const KArchiveFile *>(entry))->size());
printf("\n");
if (entry->isDirectory()) {
recursive_print(static_cast<const KArchiveDirectory *>(entry), path + it + '/');
}
}
}
void recursive_transfer(const KArchiveDirectory *dir, const QString &path, KZip *zip)
{
const QStringList lst = dir->entries();
for (const QString &it : lst) {
const KArchiveEntry *e = dir->entry(it);
qDebug() << "actual file: " << e->name();
if (e->isFile()) {
Q_ASSERT(e && e->isFile());
const KArchiveFile *f = static_cast<const KArchiveFile *>(e);
printf("FILE=%s\n", qPrintable(e->name()));
QByteArray arr(f->data());
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
printf("SIZE=%lli\n", arr.size());
#else
printf("SIZE=%i\n", arr.size());
#endif
QString str(arr);
printf("DATA=%s\n", qPrintable(str));
if (e->symLinkTarget().isEmpty()) {
zip->writeFile(path + e->name(), arr);
} else {
zip->writeSymLink(path + e->name(), e->symLinkTarget());
}
} else if (e->isDirectory()) {
recursive_transfer(static_cast<const KArchiveDirectory *>(e), path + e->name() + '/', zip);
}
}
}
static int doList(const QString &fileName)
{
KZip zip(fileName);
if (!zip.open(QIODevice::ReadOnly)) {
qWarning() << "Could not open" << fileName << "for reading. ZIP file doesn't exist or is invalid:" << zip.errorString();
return 1;
}
const KArchiveDirectory *dir = zip.directory();
recursive_print(dir, QString());
zip.close();
return 0;
}
static int doPrintAll(const QString &fileName)
{
KZip zip(fileName);
qDebug() << "Opening zip file";
if (!zip.open(QIODevice::ReadOnly)) {
qWarning() << "Could not open" << fileName << "for reading. ZIP file doesn't exist or is invalid:" << zip.errorString();
return 1;
}
const KArchiveDirectory *dir = zip.directory();
qDebug() << "Listing toplevel of zip file";
const QStringList lst = dir->entries();
for (const QString &it : lst) {
const KArchiveEntry *e = dir->entry(it);
qDebug() << "Printing" << it;
if (e->isFile()) {
Q_ASSERT(e && e->isFile());
const KArchiveFile *f = static_cast<const KArchiveFile *>(e);
const QByteArray data(f->data());
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
printf("SIZE=%lli\n", data.size());
#else
printf("SIZE=%i\n", data.size());
#endif
QString str = QString::fromUtf8(data);
printf("DATA=%s\n", qPrintable(str));
}
}
zip.close();
return 0;
}
static int doSave(const QString &fileName)
{
KZip zip(fileName);
if (!zip.open(QIODevice::WriteOnly)) {
qWarning() << "Could not open" << fileName << "for writing:" << zip.errorString();
return 1;
}
const QByteArray data = "This is the data for the main file";
bool writeOk = zip.writeFile(QStringLiteral("maindoc.txt"), data);
if (!writeOk) {
qWarning() << "Write error (main file)";
return 1;
}
const QByteArray data2 = "This is the data for the other file";
writeOk = zip.writeFile(QStringLiteral("subdir/other.txt"), data2);
if (!writeOk) {
qWarning() << "Write error (other file)";
return 1;
}
// writeOk = zip.addLocalFile("David.jpg", "picture.jpg");
// if (!writeOk) {
// qWarning() << "Write error (picture)";
// return 1;
//}
return 0;
}
static int doLoad(const QString &fileName)
{
KZip zip(fileName);
if (!zip.open(QIODevice::ReadOnly)) {
qWarning() << "Could not open" << fileName << "for reading. ZIP file doesn't exist or is invalid:" << zip.errorString();
return 1;
}
const KArchiveDirectory *dir = zip.directory();
const KArchiveEntry *mainEntry = dir->entry(QStringLiteral("maindoc.txt"));
Q_ASSERT(mainEntry && mainEntry->isFile());
const KArchiveFile *mainFile = static_cast<const KArchiveFile *>(mainEntry);
qDebug() << "maindoc.txt:" << mainFile->data();
return 0;
}
static int doPrint(const QString &fileName, const QString &entryName)
{
KZip zip(fileName);
if (!zip.open(QIODevice::ReadOnly)) {
qWarning() << "Could not open" << fileName << "for reading. ZIP file doesn't exist or is invalid:" << zip.errorString();
return 1;
}
const KArchiveDirectory *dir = zip.directory();
const KArchiveEntry *e = dir->entry(entryName);
Q_ASSERT(e && e->isFile());
const KArchiveFile *f = static_cast<const KArchiveFile *>(e);
const QByteArray arr(f->data());
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
printf("SIZE=%lli\n", arr.size());
#else
printf("SIZE=%i\n", arr.size());
#endif
QString str = QString::fromUtf8(arr);
printf("%s", qPrintable(str));
return zip.close() ? 0 : 1 /*error*/;
}
static int doCreate(const QString &archiveName, const QStringList &fileNames)
{
KZip zip(archiveName);
if (!zip.open(QIODevice::WriteOnly)) {
qWarning() << "Could not open" << archiveName << "for writing:" << zip.errorString();
return 1;
}
for (const QString &fileName : fileNames) {
QFile f(fileName);
if (!f.open(QIODevice::ReadOnly)) {
qWarning() << "Could not open" << fileName << "for reading:" << zip.errorString();
return 1;
}
zip.writeFile(fileName, f.readAll());
}
return zip.close() ? 0 : 1 /*error*/;
}
static int doUpdate(const QString &archiveName, const QString &fileName)
{
KZip zip(archiveName);
if (!zip.open(QIODevice::ReadWrite)) {
qWarning() << "Could not open" << archiveName << "for read/write:" << zip.errorString();
return 1;
}
QFile f(fileName);
if (!f.open(QIODevice::ReadOnly)) {
qWarning() << "Could not open" << fileName << "for reading:" << zip.errorString();
return 1;
}
zip.writeFile(fileName, f.readAll());
return zip.close() ? 0 : 1 /*error*/;
}
static int doTransfer(const QString &sourceFile, const QString &destFile)
{
KZip zip1(sourceFile);
KZip zip2(destFile);
if (!zip1.open(QIODevice::ReadOnly)) {
qWarning() << "Could not open" << sourceFile << "for reading. ZIP file doesn't exist or is invalid:" << zip1.errorString();
return 1;
}
if (!zip2.open(QIODevice::WriteOnly)) {
qWarning() << "Could not open" << destFile << "for writing:" << zip2.errorString();
return 1;
}
const KArchiveDirectory *dir1 = zip1.directory();
recursive_transfer(dir1, QLatin1String(""), &zip2);
zip1.close();
zip2.close();
return 0;
}
static bool save(QIODevice *device)
{
const QByteArray data = "This is some text that will be compressed.\n";
const int written = device->write(data);
if (written != data.size()) {
qWarning() << "Error writing data";
return 1;
}
// success
return 0;
}
static int doCompress(const QString &fileName)
{
KCompressionDevice device(fileName, KCompressionDevice::BZip2);
if (!device.open(QIODevice::WriteOnly)) {
qWarning() << "Could not open" << fileName << "for writing:" << device.errorString();
return 1;
}
return save(&device);
}
static bool load(QIODevice *device)
{
const QByteArray data = device->readAll();
printf("%s", data.constData());
return true;
}
static int doUncompress(const QString &fileName)
{
KCompressionDevice device(fileName, KCompressionDevice::BZip2);
if (!device.open(QIODevice::ReadOnly)) {
qWarning() << "Could not open" << fileName << "for reading:" << device.errorString();
return 1;
}
return load(&device);
}
int main(int argc, char **argv)
{
if (argc < 3) {
// ###### Note: please consider adding new tests to karchivetest (so that they can be automated)
// rather than here (interactive)
printf(
"\n"
" Usage :\n"
" ./kziptest list /path/to/existing_file.zip tests listing an existing zip\n"
" ./kziptest print-all file.zip prints contents of all files.\n"
" ./kziptest print file.zip filename prints contents of one file.\n"
" ./kziptest create file.zip filenames create a new zip file with these files.\n"
" ./kziptest update file.zip filename update filename in file.zip.\n"
" ./kziptest save file.zip save file.\n"
" ./kziptest load file.zip load file.\n"
" ./kziptest write file.bz2 write compressed file.\n"
" ./kziptest read file.bz2 read uncompressed file.\n");
return 1;
}
QCoreApplication app(argc, argv);
QString command = argv[1];
if (command == QLatin1String("list")) {
return doList(QFile::decodeName(argv[2]));
} else if (command == QLatin1String("print-all")) {
return doPrintAll(QFile::decodeName(argv[2]));
} else if (command == QLatin1String("print")) {
if (argc != 4) {
printf("usage: kziptest print archivename filename");
return 1;
}
return doPrint(QFile::decodeName(argv[2]), argv[3]);
} else if (command == QLatin1String("save")) {
return doSave(QFile::decodeName(argv[2]));
} else if (command == QLatin1String("load")) {
return doLoad(QFile::decodeName(argv[2]));
} else if (command == QLatin1String("write")) {
return doCompress(QFile::decodeName(argv[2]));
} else if (command == QLatin1String("read")) {
return doUncompress(QFile::decodeName(argv[2]));
} else if (command == QLatin1String("create")) {
if (argc < 4) {
printf("usage: kziptest create archivename filenames");
return 1;
}
const QStringList fileNames = app.arguments().mid(3);
return doCreate(QFile::decodeName(argv[2]), fileNames);
} else if (command == QLatin1String("update")) {
if (argc != 4) {
printf("usage: kziptest update archivename filename");
return 1;
}
return doUpdate(QFile::decodeName(argv[2]), QFile::decodeName(argv[3]));
} else if (command == QLatin1String("transfer")) {
if (argc != 4) {
printf("usage: kziptest transfer sourcefile destfile");
return 1;
}
return doTransfer(QFile::decodeName(argv[2]), QFile::decodeName(argv[3]));
} else {
printf("Unknown command\n");
}
}