Mercurial > code
changeset 281:88f9d8b406c6
Add Zip, safe wrapper around libzip
author | David Demelier <markand@malikania.fr> |
---|---|
date | Tue, 11 Nov 2014 14:03:42 +0100 |
parents | 91eb0583df52 |
children | 9be2cd100167 |
files | C++/Tests/Zip/CMakeLists.txt C++/Tests/Zip/data/stats.zip C++/Tests/Zip/main.cpp C++/ZipArchive.cpp C++/ZipArchive.h CMakeLists.txt cmake/FindZIP.cmake |
diffstat | 7 files changed, 788 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/Tests/Zip/CMakeLists.txt Tue Nov 11 14:03:42 2014 +0100 @@ -0,0 +1,34 @@ +# +# CMakeLists.txt -- tests for Zip +# +# Copyright (c) 2013, 2014 David Demelier <markand@malikania.fr> +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# + +project(zip) + +find_package(ZIP REQUIRED) + +set( + SOURCES + ${code_SOURCE_DIR}/C++/ZipArchive.cpp + ${code_SOURCE_DIR}/C++/ZipArchive.h + main.cpp +) + +define_test(zip "${SOURCES}") + +target_include_directories(zip PRIVATE ${ZIP_INCLUDE_DIRS}) +target_link_libraries(zip ${ZIP_LIBRARIES}) +target_compile_definitions(zip PRIVATE "SOURCE=\"${zip_SOURCE_DIR}\"" "BINARY=\"${zip_BINARY_DIR}\"")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/Tests/Zip/main.cpp Tue Nov 11 14:03:42 2014 +0100 @@ -0,0 +1,85 @@ +/* + * main.cpp -- test the zip wrapper functions + * + * Copyright (c) 2013, 2014 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <gtest/gtest.h> + +#include <ZipArchive.h> + +using namespace source; + +TEST(Basic, stat) +{ + try { + ZipArchive archive{SOURCE "/data/stats.zip"}; + ZipStat stats = archive.stat("README"); + + ASSERT_EQ(static_cast<decltype(stats.size)>(15), stats.size); + ASSERT_STREQ("README", stats.name); + } catch (const std::exception &ex) { + std::cerr << ex.what() << std::endl; + } +} + +TEST(Basic, read) +{ + try { + ZipArchive archive{SOURCE "/data/stats.zip"}; + + auto file = archive.open("README"); + auto stats = archive.stat("README"); + auto text = file.read(stats.size); + + ASSERT_EQ("This is a test\n", text); + } catch (const std::exception &ex) { + std::cerr << "warning: " << ex.what() << std::endl; + } +} + +TEST(Basic, write) +{ + remove(BINARY "/output.zip"); + + // Open first and save some data + try { + ZipArchive archive{BINARY "/output.zip", ZIP_CREATE}; + + archive.add(Buffer{"hello world!"}, "DATA"); + } catch (const std::exception &ex) { + std::cerr << "warning: " << ex.what() << std::endl; + } + + try { + ZipArchive archive{BINARY "/output.zip"}; + + auto stats = archive.stat("DATA"); + auto file = archive.open("DATA"); + auto content = file.read(stats.size); + + ASSERT_EQ(static_cast<decltype(stats.size)>(12), stats.size); + ASSERT_EQ("hello world!", content); + } catch (const std::exception &ex) { + std::cerr << "warning: " << ex.what() << std::endl; + } +} + +int main(int argc, char **argv) +{ + testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/ZipArchive.cpp Tue Nov 11 14:03:42 2014 +0100 @@ -0,0 +1,248 @@ +/* + * ZipArchive.cpp -- wrapper around libzip + * + * Copyright (c) 2013, 2014 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <cerrno> +#include <cstdlib> +#include <cstring> +#include <stdexcept> + +#include "ZipArchive.h" + +namespace source { + +/* -------------------------------------------------------- + * Buffer (zip_source_buffer) + * -------------------------------------------------------- */ + +Buffer::Buffer(std::string data) + : m_data(std::move(data)) +{ +} + +struct zip_source *Buffer::source(struct zip *archive) const +{ + auto size = m_data.size(); + auto data = static_cast<char *>(std::malloc(size)); + + if (!data) + throw std::runtime_error(std::strerror(errno)); + + std::memcpy(data, m_data.data(), size); + + auto src = zip_source_buffer(archive, data, size, 1); + + if (src == nullptr) { + std::free(data); + throw std::runtime_error(zip_strerror(archive)); + } + + return src; +} + +/* -------------------------------------------------------- + * File (zip_source_file) + * -------------------------------------------------------- */ + +File::File(std::string path, ZipUint64 start, ZipInt64 length) + : m_path(std::move(path)) + , m_start(start) + , m_length(length) +{ +} + +struct zip_source *File::source(struct zip *archive) const +{ + auto src = zip_source_file(archive, m_path.c_str(), m_start, m_length); + + if (!src) + throw std::runtime_error(zip_strerror(archive)); + + return src; +} + +} // !source + +/* -------------------------------------------------------- + * ZipArchive + * ------------------------------------------------------- */ + +ZipArchive::ZipArchive(const std::string &path, ZipFlags flags) + : m_handle(nullptr, nullptr) +{ + int error; + struct zip *archive = zip_open(path.c_str(), flags, &error); + + if (archive == nullptr) + { + char buf[128]{}; + + zip_error_to_str(buf, sizeof (buf), error, errno); + + throw std::runtime_error(buf); + } + + m_handle = { archive, zip_close }; +} + +void ZipArchive::setFileComment(ZipUint64 index, const std::string &text, ZipFlags flags) +{ + auto size = text.size(); + auto cstr = (size == 0) ? nullptr : text.c_str(); + + if (zip_file_set_comment(m_handle.get(), index, cstr, size, flags) < 0) + throw std::runtime_error(zip_strerror(m_handle.get())); +} + +std::string ZipArchive::getFileComment(ZipUint64 index, ZipFlags flags) const +{ + zip_uint32_t length{}; + auto text = zip_file_get_comment(m_handle.get(), index, &length, flags); + + if (text == nullptr) + throw std::runtime_error(zip_strerror(m_handle.get())); + + return { text, length }; +} + +void ZipArchive::setComment(const std::string &comment) +{ + if (zip_set_archive_comment(m_handle.get(), comment.c_str(), comment.size()) < 0) + throw std::runtime_error(zip_strerror(m_handle.get())); +} + +std::string ZipArchive::getComment(ZipFlags flags) const +{ + int length{}; + auto text = zip_get_archive_comment(m_handle.get(), &length, flags); + + if (text == nullptr) + throw std::runtime_error(zip_strerror(m_handle.get())); + + return { text, static_cast<size_t>(length) }; +} + +ZipInt64 ZipArchive::find(const std::string &name, ZipFlags flags) +{ + auto index = zip_name_locate(m_handle.get(), name.c_str(), flags); + + if (index < 0) + throw std::runtime_error(zip_strerror(m_handle.get())); + + return index; +} + +ZipStat ZipArchive::stat(const std::string &name, ZipFlags flags) +{ + ZipStat st; + + if (zip_stat(m_handle.get(), name.c_str(), flags, &st) < 0) + throw std::runtime_error(zip_strerror(m_handle.get())); + + return st; +} + +ZipStat ZipArchive::stat(ZipUint64 index, ZipFlags flags) +{ + ZipStat st; + + if (zip_stat_index(m_handle.get(), index, flags, &st) < 0) + throw std::runtime_error(zip_strerror(m_handle.get())); + + return st; +} + +ZipInt64 ZipArchive::add(const ZipSource &source, const std::string &name, ZipFlags flags) +{ + auto src = source.source(m_handle.get()); + auto ret = zip_file_add(m_handle.get(), name.c_str(), src, flags); + + if (ret < 0) { + zip_source_free(src); + throw std::runtime_error(zip_strerror(m_handle.get())); + } + + return ret; +} + +ZipInt64 ZipArchive::addDirectory(const std::string &directory, ZipFlags flags) +{ + auto ret = zip_dir_add(m_handle.get(), directory.c_str(), flags); + + if (ret < 0) + throw std::runtime_error(zip_strerror(m_handle.get())); + + return ret; +} + +void ZipArchive::replace(const ZipSource &source, ZipUint64 index, ZipFlags flags) +{ + auto src = source.source(m_handle.get()); + + if (zip_file_replace(m_handle.get(), index, src, flags) < 0) { + zip_source_free(src); + throw std::runtime_error(zip_strerror(m_handle.get())); + } +} + +ZipFile ZipArchive::open(const std::string &name, ZipFlags flags, const std::string &password) +{ + struct zip_file *file; + + if (password.size() > 0) + file = zip_fopen_encrypted(m_handle.get(), name.c_str(), flags, password.c_str()); + else + file = zip_fopen(m_handle.get(), name.c_str(), flags); + + if (file == nullptr) + throw std::runtime_error(zip_strerror(m_handle.get())); + + return file; +} + +ZipFile ZipArchive::open(ZipUint64 index, ZipFlags flags, const std::string &password) +{ + struct zip_file *file; + + if (password.size() > 0) + file = zip_fopen_index_encrypted(m_handle.get(), index, flags, password.c_str()); + else + file = zip_fopen_index(m_handle.get(), index, flags); + + if (file == nullptr) + throw std::runtime_error(zip_strerror(m_handle.get())); + + return file; +} + +void ZipArchive::rename(ZipUint64 index, const std::string &name, ZipFlags flags) +{ + if (zip_file_rename(m_handle.get(), index, name.c_str(), flags) < 0) + throw std::runtime_error(zip_strerror(m_handle.get())); +} + +void ZipArchive::setFileCompression(ZipUint64 index, ZipInt32 comp, ZipUint32 flags) +{ + if (zip_set_file_compression(m_handle.get(), index, comp, flags) < 0) + throw std::runtime_error(zip_strerror(m_handle.get())); +} + +void ZipArchive::remove(ZipUint64 index) +{ + if (zip_delete(m_handle.get(), index) < 0) + throw std::runtime_error(zip_strerror(m_handle.get())); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/ZipArchive.h Tue Nov 11 14:03:42 2014 +0100 @@ -0,0 +1,374 @@ +/* + * ZipArchive.h -- wrapper around libzip + * + * Copyright (c) 2013, 2014 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _ZIP_ARCHIVE_H_ +#define _ZIP_ARCHIVE_H_ + +#include <memory> +#include <string> + +#include <zip.h> + +using ZipStat = struct zip_stat; +using ZipSourceCommand = enum zip_source_cmd; +using ZipCallback = zip_source_callback; + +using ZipFlags = zip_flags_t; +using ZipInt8 = zip_int8_t; +using ZipUint8 = zip_uint8_t; +using ZipInt16 = zip_int16_t; +using ZipUint16 = zip_uint16_t; +using ZipInt32 = zip_int32_t; +using ZipUint32 = zip_uint32_t; +using ZipInt64 = zip_int64_t; +using ZipUint64 = zip_uint64_t; + +/** + * @class ZipSource + * @brief Source for adding file + */ +class ZipSource { +public: + /** + * Default constructor. + */ + ZipSource() = default; + + /** + * Virtual destructor. + */ + virtual ~ZipSource() = default; + + /** + * Create a zip_source structure. Must not be null, throw an exception + * instead. + * + * @return a zip_source ready to be used + * @post must not return null + */ + virtual struct zip_source *source(struct zip *zip) const = 0; +}; + +/** + * @class ZipFile + * @brief File for reading + */ +class ZipFile { +private: + std::unique_ptr<struct zip_file, int (*)(struct zip_file *)> m_handle; + + ZipFile(const ZipFile &) = delete; + ZipFile &operator=(const ZipFile &) = delete; +public: + /** + * Create a ZipFile with a zip_file structure. + * + * @param file the file ready to be used + */ + inline ZipFile(struct zip_file *file) + : m_handle(file, zip_fclose) + { + } + + /** + * Move constructor defaulted. + * + * @param other the other ZipFile + */ + ZipFile(ZipFile &&other) = default; + + /** + * Move operator defaulted. + * + * @param other the other ZipFile + * @return *this + */ + ZipFile &operator=(ZipFile &&) = default; + + /** + * Read some data. + * + * @param data the destination buffer + * @param length the length + * @return the number of bytes written or -1 on failure + */ + inline int read(void *data, ZipUint64 length) noexcept + { + return zip_fread(m_handle.get(), data, length); + } + + /** + * Read some data to a fixed size array. + * + * @param data the array + * @return the number of bytes written or -1 on failure + */ + template <size_t Size> + inline int read(char (&data)[Size]) noexcept + { + return read(data, Size); + } + + /** + * Optimized function for reading all characters with only one allocation. + * Ideal for combining with ZipArchive::stat. + * + * @param length the length of the file + * @return the whole string + * @see ZipArchive::stat + */ + std::string read(unsigned length) + { + std::string result; + + result.resize(length); + auto count = read(&result[0], length); + + if (count < 0) + return ""; + + return result; + } +}; + +namespace source { + +/** + * @class Buffer + * @brief Create a source from a buffer + */ +class Buffer : public ZipSource { +private: + std::string m_data; + +public: + /** + * Buffer constructor. Moves the data. + * + * @param data the data + */ + Buffer(std::string data); + + /** + * @copydoc ZipSource::source + */ + struct zip_source *source(struct zip *archive) const override; +}; + +/** + * @class File + * @brief Create a source from a file on the disk + */ +class File : public ZipSource { +private: + std::string m_path; + ZipUint64 m_start; + ZipInt64 m_length; + +public: + /** + * File constructor. + * + * @param path the path to the file + * @param start the beginning in the file + * @param length the maximum length + */ + File(std::string path, ZipUint64 start = 0, ZipInt64 length = -1); + + /** + * @copydoc ZipSource::source + */ + struct zip_source *source(struct zip *archive) const override; +}; + +} // !source + +/** + * @class ZipArchive + * @brief Safe wrapper on the struct zip structure + */ +class ZipArchive { +private: + using Handle = std::unique_ptr<struct zip, int (*)(struct zip *)>; + + Handle m_handle; + + ZipArchive(const ZipArchive &) = delete; + ZipArchive &operator=(const ZipArchive &) = delete; + +public: + /** + * Open an archive on the disk. + * + * @param path the path + * @param flags the optional flags + */ + ZipArchive(const std::string &path, ZipFlags flags = 0); + + /** + * Move constructor defaulted. + * + * @param other the other ZipArchive + */ + ZipArchive(ZipArchive &&other) noexcept = default; + + /** + * Move operator defaulted. + * + * @param other the other ZipArchive + * @return *this + */ + ZipArchive &operator=(ZipArchive &&other) noexcept = default; + + /** + * Set a comment on a file. + * + * @param index the file index in the archive + * @param text the text or empty to remove the comment + * @param flags the optional flags + */ + void setFileComment(ZipUint64 index, const std::string &text = "", ZipFlags flags = 0); + + /** + * Get a comment from a file. + * + * @param index the file index in the archive + * @param flags the optional flags + * @return the comment + */ + std::string getFileComment(ZipUint64 index, ZipFlags flags = 0) const; + + /** + * Set the archive comment. + * + * @param comment the comment + */ + void setComment(const std::string &comment); + + /** + * Get the archive comment. + * + * @param flags the optional flags + * @return the comment + */ + std::string getComment(ZipFlags flags = 0) const; + + /** + * Locate a file on the archive. + * + * @param name the name + * @param flags the optional flags + * @return the index + */ + ZipInt64 find(const std::string &name, ZipFlags flags = 0); + + /** + * Get information about a file. + * + * @param name the name + * @param flags the optional flags + * @return the structure + */ + ZipStat stat(const std::string &name, ZipFlags flags = 0); + + /** + * Get information about a file. Overloaded function. + * + * @param index the file index in the archive + * @param flags the optional flags + * @return the structure + */ + ZipStat stat(ZipUint64 index, ZipFlags flags = 0); + + /** + * Add a file to the archive. + * + * @param source the source + * @param name the name entry in the archive + * @param flags the optional flags + * @return the new index in the archive + * @see source::File + * @see source::Buffer + */ + ZipInt64 add(const ZipSource &source, const std::string &name, ZipFlags flags = 0); + + /** + * Add a directory to the archive. Not a directory from the disk. + * + * @param directory the directory name + * @param flags the optional flags + * @return the new index in the archive + */ + ZipInt64 addDirectory(const std::string &directory, ZipFlags flags = 0); + + /** + * Replace an existing file in the archive. + * + * @param source the source + * @param index the file index in the archiev + * @param flags the optional flags + */ + void replace(const ZipSource &source, ZipUint64 index, ZipFlags flags = 0); + + /** + * Open a file in the archive. + * + * @param name the name + * @param flags the optional flags + * @param password the optional password + * @return the opened file + */ + ZipFile open(const std::string &name, ZipFlags flags = 0, const std::string &password = ""); + + /** + * Open a file in the archive. Overloaded function. + * + * @param index the file index in the archive + * @param flags the optional flags + * @param password the optional password + * @return the opened file + */ + ZipFile open(ZipUint64 index, ZipFlags flags = 0, const std::string &password = ""); + + /** + * Rename an existing entry in the archive. + * + * @param index the file index in the archive + * @param name the new name + * @param flags the optional flags + */ + void rename(ZipUint64 index, const std::string &name, ZipFlags flags = 0); + + /** + * Set file compression. + * + * @param index the file index in the archive + * @param comp the compression + * @param flags the optional flags + */ + void setFileCompression(ZipUint64 index, ZipInt32 comp, ZipUint32 flags = 0); + + /** + * Delete a file from the archive. + * + * @param index the file index in the archive + */ + void remove(ZipUint64 index); +}; + +#endif // !_ZIP_ARCHIVE_H_
--- a/CMakeLists.txt Fri Oct 24 18:28:39 2014 +0200 +++ b/CMakeLists.txt Tue Nov 11 14:03:42 2014 +0100 @@ -59,6 +59,7 @@ option(WITH_TREENODE "Enable treenode tests" On) option(WITH_UTF8 "Enable Utf8 functions tests" On) option(WITH_XMLPARSER "Enable XML tests" On) +option(WITH_ZIP "Enable ZipArchive tests" On) if (UNIX) option(WITH_XDG "Enable XDG standard directories tests" On) @@ -107,3 +108,7 @@ if (WITH_XDG AND UNIX) add_subdirectory(C++/Tests/Xdg) endif () + +if (WITH_ZIP) + add_subdirectory(C++/Tests/Zip) +endif ()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cmake/FindZIP.cmake Tue Nov 11 14:03:42 2014 +0100 @@ -0,0 +1,42 @@ +find_package(ZLIB QUIET) + +find_path( + ZIP_INCLUDE_DIR + NAMES zip.h +) + +find_library( + ZIP_LIBRARY + NAMES zip libzip +) + +find_path( + ZIPCONF_INCLUDE_DIR + NAMES zipconf.h +) + +if (NOT ZIPCONF_INCLUDE_DIR) + # zipconf.h is sometimes directly in the include/ folder but on some systems + # like Windows, it is installed in the lib/ directory. + get_filename_component(_ZIP_PRIVATE_LIBRARY "${ZIP_LIBRARY}" DIRECTORY) + + find_path( + ZIPCONF_INCLUDE_DIR + NAMES zipconf.h + PATHS "${_ZIP_PRIVATE_LIBRARY}/libzip/include" + ) +endif () + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args( + ZIP + REQUIRED_VARS ZLIB_LIBRARIES ZLIB_INCLUDE_DIRS ZIP_LIBRARY ZIP_INCLUDE_DIR ZIPCONF_INCLUDE_DIR +) + +if (ZIP_FOUND) + set(ZIP_LIBRARIES ${ZIP_LIBRARY} ${ZLIB_LIBRARIES}) + set(ZIP_INCLUDE_DIRS ${ZIP_INCLUDE_DIR} ${ZIPCONF_INCLUDE_DIR} ${ZLIB_INCLUDE_DIRS}) +endif () + +mark_as_advanced(ZIP_LIBRARY ZIP_INCLUDE_DIR ZIPCONF_INCLUDE_DIR)