use streams to access zip files and their contents instead of std::stringstream to reduce memory usage, replaces miniz with zlib, closes #66

This commit is contained in:
Thomas Fussell 2016-10-30 15:48:40 -04:00
parent 6d749aaa91
commit 5b4de6a150
23 changed files with 912 additions and 6210 deletions

3
.gitmodules vendored
View File

@ -22,3 +22,6 @@
path = third-party/botan
url = https://github.com/randombit/botan
branch = master
[submodule "third-party/zlib"]
path = third-party/zlib
url = https://github.com/madler/zlib.git

View File

@ -1,149 +0,0 @@
// Copyright (c) 2014-2016 Thomas Fussell
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, WRISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE
//
// @license: http://www.opensource.org/licenses/mit-license.php
// @author: see AUTHORS file
#pragma once
#include <cstdint>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
#include <xlnt/xlnt_config.hpp>
#include <xlnt/utils/path.hpp>
// Note: this comes from https://github.com/tfussell/miniz-cpp
struct mz_zip_archive_tag;
namespace xlnt {
/// <summary>
/// Information about a specific file in zip_file.
/// </summary>
struct XLNT_API zip_info
{
/// <summary>
/// A struct representing a particular date and time.
/// </summary>
struct date_time_t
{
int year;
int month;
int day;
int hours;
int minutes;
int seconds;
};
/// <summary>
/// Default constructor for zip_info.
/// </summary>
zip_info();
date_time_t date_time;
path filename;
std::string comment;
std::string extra;
uint16_t create_system;
uint16_t create_version;
uint16_t extract_version;
uint16_t flag_bits;
std::size_t volume;
uint32_t internal_attr;
uint32_t external_attr;
std::size_t header_offset;
uint32_t crc;
std::size_t compress_size;
std::size_t file_size;
};
/// <summary>
/// A compressed archive file that exists in memory which can read
/// or write to and from the filesystem, std::iostreams, and byte vectors.
/// </summary>
class XLNT_API zip_file
{
public:
zip_file();
zip_file(const path &filename);
zip_file(const std::vector<uint8_t> &bytes);
zip_file(std::istream &stream);
~zip_file();
// to/from file
void load(const path &filename);
void save(const path &filename);
// to/from byte vector
void load(const std::vector<std::uint8_t> &bytes);
void save(std::vector<std::uint8_t> &bytes);
// to/from iostream
void load(std::istream &stream);
void save(std::ostream &stream);
void reset();
bool has_file(const path &name);
bool has_file(const zip_info &name);
zip_info getinfo(const path &name);
std::vector<zip_info> infolist();
std::vector<path> namelist();
std::ostream &open(const path &name);
std::ostream &open(const zip_info &name);
std::string read(const path &name);
std::string read(const zip_info &name);
bool check_crc();
void write_file(const path &source_file);
void write_file(const path &source_file, const path &archive_path);
void write_string(const std::string &string, const path &archive_path);
void write_string(const std::string &string, const zip_info &archive_path);
path get_filename() const;
std::string comment;
private:
void start_read();
void start_write();
void append_comment();
void remove_comment();
zip_info getinfo(int index);
std::unique_ptr<mz_zip_archive_tag> archive_;
std::vector<char> buffer_;
std::stringstream open_stream_;
path filename_;
};
} // namespace xlnt

View File

@ -37,7 +37,6 @@
// packaging
#include <xlnt/packaging/manifest.hpp>
#include <xlnt/packaging/relationship.hpp>
#include <xlnt/packaging/zip_file.hpp>
// styles
#include <xlnt/styles/alignment.hpp>

View File

@ -67,7 +67,9 @@ include_directories(${XLNT_INCLUDE_DIR}
${THIRD_PARTY_DIR}/libstudxml
${THIRD_PARTY_DIR}/utfcpp/source
${THIRD_PARTY_DIR}/pole
${THIRD_PARTY_DIR}/botan)
${THIRD_PARTY_DIR}/botan
${THIRD_PARTY_DIR}/partio
${THIRD_PARTY_DIR}/zlib)
file(GLOB ROOT_HEADERS ${XLNT_INCLUDE_DIR}/xlnt/*.hpp)
file(GLOB CELL_HEADERS ${XLNT_INCLUDE_DIR}/xlnt/cell/*.hpp)

View File

@ -20,6 +20,7 @@
//
// @license: http://www.opensource.org/licenses/mit-license.php
// @author: see AUTHORS file
#include <cctype>
#include <numeric> // for std::accumulate
@ -27,10 +28,10 @@
#include <detail/constants.hpp>
#include <detail/custom_value_traits.hpp>
#include <detail/workbook_impl.hpp>
#include <detail/zip.hpp>
#include <xlnt/cell/cell.hpp>
#include <xlnt/utils/path.hpp>
#include <xlnt/packaging/manifest.hpp>
#include <xlnt/packaging/zip_file.hpp>
#include <xlnt/workbook/const_worksheet_iterator.hpp>
#include <xlnt/workbook/workbook.hpp>
#include <xlnt/worksheet/worksheet.hpp>
@ -98,13 +99,13 @@ xlnt::color read_color(xml::parser &parser)
return result;
}
std::vector<xlnt::relationship> read_relationships(const xlnt::path &part, xlnt::zip_file &archive)
std::vector<xlnt::relationship> read_relationships(const xlnt::path &part, Partio::ZipFileReader &archive)
{
std::vector<xlnt::relationship> relationships;
if (!archive.has_file(part)) return relationships;
if (!archive.Has_File(part.string())) return relationships;
std::istringstream rels_stream(archive.read(part));
xml::parser parser(rels_stream, part.string());
std::unique_ptr<std::istream> rels_stream(archive.Get_File(part.string(), true));
xml::parser parser(*rels_stream, part.string());
xlnt::uri source(part.string());
@ -149,21 +150,9 @@ xlsx_consumer::xlsx_consumer(workbook &target)
{
}
void xlsx_consumer::read(const path &source)
{
source_.load(source);
populate_workbook();
}
void xlsx_consumer::read(std::istream &source)
{
source_.load(source);
populate_workbook();
}
void xlsx_consumer::read(const std::vector<std::uint8_t> &source)
{
source_.load(source);
archive_.reset(new Partio::ZipFileReader(source));
populate_workbook();
}
@ -181,8 +170,8 @@ void xlsx_consumer::populate_workbook()
for (const auto &rel : manifest.get_relationships(path("/")))
{
std::istringstream parser_stream(source_.read(rel.get_target().get_path()));
xml::parser parser(parser_stream, rel.get_target().get_path().string());
std::unique_ptr<std::istream> parser_stream(archive_->Get_File(rel.get_target().get_path().string(), true));
xml::parser parser(*parser_stream, rel.get_target().get_path().string());
parser_ = &parser;
switch (rel.get_type())
@ -246,11 +235,11 @@ void xlsx_consumer::populate_workbook()
for (const auto &rel : manifest.get_relationships(workbook_rel.get_target().get_path()))
{
path part_path(rel.get_source().get_path().parent().append(rel.get_target().get_path()));
std::istringstream parser_stream(source_.read(part_path));
std::unique_ptr<std::istream> parser_stream(archive_->Get_File(part_path.string(), true));
auto using_namespaces = rel.get_type() == relationship::type::styles;
auto receive = xml::parser::receive_default
| (using_namespaces ? xml::parser::receive_namespace_decls : 0);
xml::parser parser(parser_stream, rel.get_target().get_path().string(), receive);
xml::parser parser(*parser_stream, part_path.string(), receive);
parser_ = &parser;
switch (rel.get_type())
@ -279,9 +268,9 @@ void xlsx_consumer::populate_workbook()
for (const auto &rel : manifest.get_relationships(workbook_rel.get_target().get_path()))
{
path part_path(rel.get_source().get_path().parent().append(rel.get_target().get_path()));
std::istringstream parser_stream(source_.read(part_path));
std::unique_ptr<std::istream> parser_stream(archive_->Get_File(part_path.string(), true));
auto receive = xml::parser::receive_default | xml::parser::receive_namespace_decls;
xml::parser parser(parser_stream, rel.get_target().get_path().string(), receive);
xml::parser parser(*parser_stream, rel.get_target().get_path().string(), receive);
parser_ = &parser;
switch (rel.get_type())
@ -317,15 +306,16 @@ void xlsx_consumer::read_manifest()
{
path package_rels_path("_rels/.rels");
if (!source_.has_file(package_rels_path))
if (!archive_->Has_File(package_rels_path.string()))
{
throw invalid_file("missing package rels");
}
auto package_rels = read_relationships(package_rels_path, source_);
auto package_rels = read_relationships(package_rels_path, *archive_);
std::istringstream parser_stream(source_.read(path("[Content_Types].xml")));
xml::parser parser(parser_stream, "[Content_Types].xml");
std::unique_ptr<std::istream> parser_stream(archive_->Get_File("[Content_Types].xml", true));
//std::string stream_string((std::istreambuf_iterator<char>(*parser_stream)), std::istreambuf_iterator<char>());
xml::parser parser(*parser_stream, "[Content_Types].xml");
auto &manifest = target_.get_manifest();
@ -365,18 +355,23 @@ void xlsx_consumer::read_manifest()
package_rel.get_id());
}
for (const auto &relationship_source : source_.infolist())
{
if (relationship_source.filename == path("_rels/.rels")
|| relationship_source.filename.extension() != "rels") continue;
std::vector<std::string> file_list;
archive_->Get_File_List(file_list);
path part(relationship_source.filename.parent().parent());
part = part.append(relationship_source.filename.split_extension().first);
for (const auto &relationship_source_string : file_list)
{
auto relationship_source = path(relationship_source_string);
if (relationship_source == path("_rels/.rels")
|| relationship_source.extension() != "rels") continue;
path part(relationship_source.parent().parent());
part = part.append(relationship_source.split_extension().first);
uri source(part.string());
path source_directory = part.parent();
auto part_rels = read_relationships(relationship_source.filename, source_);
auto part_rels = read_relationships(relationship_source, *archive_);
for (const auto part_rel : part_rels)
{
@ -2017,9 +2012,10 @@ void xlsx_consumer::read_worksheet(const std::string &rel_id)
}
part_path = std::accumulate(split_part_path.begin(), split_part_path.end(), path(""),
[](const path &a, const std::string &b) { return a.append(b); });
std::istringstream parser_stream(source_.read(part_path));
std::unique_ptr<std::istream> parser_stream(archive_->Get_File(part_path.string(), true));
auto receive = xml::parser::receive_default;
xml::parser parser(parser_stream, rel.get_target().get_path().string(), receive);
xml::parser parser(*parser_stream, rel.get_target().get_path().string(), receive);
parser_ = &parser;
switch (rel.get_type())

View File

@ -25,12 +25,15 @@
#include <cstdint>
#include <iostream>
#include <memory>
#include <unordered_map>
#include <vector>
#include <detail/include_libstudxml.hpp>
#include <xlnt/xlnt_config.hpp>
#include <xlnt/packaging/zip_file.hpp>
namespace Partio {
class ZipFileReader;
}
namespace xlnt {
@ -44,23 +47,15 @@ namespace detail {
/// <summary>
/// Handles writing a workbook into an XLSX file.
/// </summary>
class XLNT_API xlsx_consumer
class xlsx_consumer
{
public:
xlsx_consumer(workbook &destination);
void read(const path &source);
void read(std::istream &source);
void read(const std::vector<std::uint8_t> &source);
void read(const path &source, const std::string &password);
void read(std::istream &source, const std::string &password);
void read(const std::vector<std::uint8_t> &source, const std::string &password);
private:
/// <summary>
/// Ignore all remaining elements at the same depth in the current XML parser.
@ -223,7 +218,7 @@ private:
/// <summary>
/// The ZIP file containing the files that make up the OOXML package.
/// </summary>
zip_file source_;
std::unique_ptr<Partio::ZipFileReader> archive_;
/// <summary>
/// Map of sheet titles to relationship IDs.

View File

@ -25,6 +25,7 @@
#include <botan_all.h>
#include <include_libstudxml.hpp>
#include <detail/vector_streambuf.hpp>
#include <detail/xlsx_consumer.hpp>
#include <xlnt/utils/exceptions.hpp>
#include <xlnt/workbook/workbook.hpp>
@ -271,9 +272,14 @@ std::vector<std::uint8_t> decrypt_xlsx_standard(const std::vector<std::uint8_t>
//todo: verify here
return aes(key_derived, {}, std::vector<std::uint8_t>(
std::size_t package_offset = 0;
auto decrypted_size = read_int<std::uint64_t>(package_offset, encrypted_package);
auto decrypted = aes(key_derived, {}, std::vector<std::uint8_t>(
encrypted_package.begin() + 8, encrypted_package.end()),
cipher_chaining::ecb, cipher_direction::decryption);
decrypted.resize(decrypted_size);
return decrypted;
}
@ -567,23 +573,14 @@ std::vector<std::uint8_t> decrypt_xlsx(const std::vector<std::uint8_t> &bytes, c
return decrypt_xlsx_standard(encryption_info, password, encrypted_package);
}
void xlsx_consumer::read(const std::vector<std::uint8_t> &source, const std::string &password)
{
source_.load(decrypt_xlsx(source, password));
populate_workbook();
}
void xlsx_consumer::read(std::istream &source, const std::string &password)
{
std::vector<std::uint8_t> data((std::istreambuf_iterator<char>(source)),
std::istreambuf_iterator<char>());
return read(data, password);
}
void xlsx_consumer::read(const path &source, const std::string &password)
{
std::ifstream file_stream(source.string(), std::iostream::binary);
return read(file_stream, password);
std::vector<std::uint8_t> data((std::istreambuf_iterator<char>(source)),
(std::istreambuf_iterator<char>()));
const auto decrypted = decrypt_xlsx(data, password);
vector_istreambuf decrypted_buffer(decrypted);
std::istream decrypted_stream(&decrypted_buffer);
read(decrypted_stream);
}
} // namespace detail

View File

@ -31,7 +31,6 @@
#include <xlnt/cell/cell.hpp>
#include <xlnt/utils/path.hpp>
#include <xlnt/packaging/manifest.hpp>
#include <xlnt/packaging/zip_file.hpp>
#include <xlnt/workbook/const_worksheet_iterator.hpp>
#include <xlnt/workbook/workbook.hpp>
#include <xlnt/workbook/workbook_view.hpp>
@ -83,22 +82,11 @@ xlsx_producer::xlsx_producer(const workbook &target) : source_(target)
{
}
void xlsx_producer::write(const path &destination)
{
populate_archive();
destination_.save(destination);
}
void xlsx_producer::write(std::ostream &destination)
{
Partio::ZipFileWriter archive(destination);
archive_ = &archive;
populate_archive();
destination_.save(destination);
}
void xlsx_producer::write(std::vector<std::uint8_t> &destination)
{
populate_archive();
destination_.save(destination);
}
// Part Writing Methods
@ -112,11 +100,7 @@ void xlsx_producer::populate_archive()
for (auto &rel : root_rels)
{
std::ostringstream serializer_stream;
xml::serializer serializer(serializer_stream, rel.get_target().get_path().string());
serializer_ = &serializer;
bool write_document = true;
begin_part(rel.get_target().get_path());
switch (rel.get_type())
{
@ -138,57 +122,73 @@ void xlsx_producer::populate_archive()
case relationship::type::thumbnail:
write_thumbnail(rel);
write_document = false;
break;
default:
break;
}
if (write_document)
{
destination_.write_string(serializer_stream.str(), rel.get_target().get_path());
}
}
// Unknown Parts
void write_unknown_parts();
void write_unknown_relationships();
end_part();
}
void xlsx_producer::end_part()
{
if (current_part_serializer_)
{
current_part_serializer_.reset();
}
if (current_part_stream_)
{
current_part_stream_.reset();
}
}
void xlsx_producer::begin_part(const path &part)
{
end_part();
current_part_stream_.reset(archive_->Add_File(part.string(), true));
current_part_serializer_.reset(new xml::serializer(*current_part_stream_, part.string()));
}
// Package Parts
void xlsx_producer::write_content_types()
{
std::ostringstream content_types_stream;
xml::serializer content_types_serializer(content_types_stream, "[Content_Types].xml");
const auto content_types_path = path("[Content_Types].xml");
begin_part(content_types_path);
const auto xmlns = "http://schemas.openxmlformats.org/package/2006/content-types"s;
content_types_serializer.start_element(xmlns, "Types");
content_types_serializer.namespace_decl(xmlns, "");
serializer().start_element(xmlns, "Types");
serializer().namespace_decl(xmlns, "");
for (const auto &extension : source_.get_manifest().get_extensions_with_default_types())
{
content_types_serializer.start_element(xmlns, "Default");
content_types_serializer.attribute("Extension", extension);
content_types_serializer.attribute("ContentType",
serializer().start_element(xmlns, "Default");
serializer().attribute("Extension", extension);
serializer().attribute("ContentType",
source_.get_manifest().get_default_type(extension));
content_types_serializer.end_element(xmlns, "Default");
serializer().end_element(xmlns, "Default");
}
for (const auto &part : source_.get_manifest().get_parts_with_overriden_types())
{
content_types_serializer.start_element(xmlns, "Override");
content_types_serializer.attribute("PartName", part.resolve(path("/")).string());
content_types_serializer.attribute("ContentType",
serializer().start_element(xmlns, "Override");
serializer().attribute("PartName", part.resolve(path("/")).string());
serializer().attribute("ContentType",
source_.get_manifest().get_override_type(part));
content_types_serializer.end_element(xmlns, "Override");
serializer().end_element(xmlns, "Override");
}
content_types_serializer.end_element(xmlns, "Types");
destination_.write_string(content_types_stream.str(), path("[Content_Types].xml"));
serializer().end_element(xmlns, "Types");
}
void xlsx_producer::write_extended_properties(const relationship &rel)
@ -509,9 +509,8 @@ void xlsx_producer::write_workbook(const relationship &rel)
for (const auto &child_rel : workbook_rels)
{
std::ostringstream child_stream;
xml::serializer child_serializer(child_stream, child_rel.get_target().get_path().string());
serializer_ = &child_serializer;
path archive_path(child_rel.get_source().get_path().parent().append(child_rel.get_target().get_path()));
begin_part(archive_path);
switch (child_rel.get_type())
{
@ -574,9 +573,6 @@ void xlsx_producer::write_workbook(const relationship &rel)
default:
break;
}
path archive_path(child_rel.get_source().get_path().parent().append(child_rel.get_target().get_path()));
destination_.write_string(child_stream.str(), archive_path);
}
}
@ -2223,24 +2219,6 @@ void xlsx_producer::write_worksheet(const relationship &rel)
for (const auto &child_rel : worksheet_rels)
{
std::ostringstream child_stream;
xml::serializer child_serializer(child_stream, child_rel.get_target().get_path().string());
serializer_ = &child_serializer;
switch (child_rel.get_type())
{
case relationship::type::comments:
write_comments(child_rel, ws, cells_with_comments);
break;
case relationship::type::vml_drawing:
write_vml_drawings(child_rel, ws, cells_with_comments);
break;
default:
break;
}
path archive_path(worksheet_part.parent().append(child_rel.get_target().get_path()));
auto split_part_path = archive_path.split();
auto part_path_iter = split_part_path.begin();
@ -2256,7 +2234,22 @@ void xlsx_producer::write_worksheet(const relationship &rel)
}
archive_path = std::accumulate(split_part_path.begin(), split_part_path.end(), path(""),
[](const path &a, const std::string &b) { return a.append(b); });
destination_.write_string(child_stream.str(), archive_path);
begin_part(archive_path);
switch (child_rel.get_type())
{
case relationship::type::comments:
write_comments(child_rel, ws, cells_with_comments);
break;
case relationship::type::vml_drawing:
write_vml_drawings(child_rel, ws, cells_with_comments);
break;
default:
break;
}
}
}
}
@ -2506,13 +2499,15 @@ void xlsx_producer::write_unknown_relationships()
void xlsx_producer::write_thumbnail(const relationship &rel)
{
const auto &thumbnail = source_.get_thumbnail();
std::string thumbnail_string(thumbnail.begin(), thumbnail.end());
destination_.write_string(thumbnail_string, rel.get_target().get_path());
std::unique_ptr<std::ostream> thumbnail_stream(
archive_->Add_File(rel.get_target().get_path().string(), true));
std::for_each(thumbnail.begin(), thumbnail.end(),
[&thumbnail_stream](std::uint8_t b) { *thumbnail_stream << b; });
}
xml::serializer &xlsx_producer::serializer()
{
return *serializer_;
return *current_part_serializer_;
}
std::string xlsx_producer::write_bool(bool boolean) const
@ -2535,33 +2530,31 @@ void xlsx_producer::write_relationships(const std::vector<xlnt::relationship> &r
parent = path(parent.string().substr(1));
}
std::ostringstream rels_stream;
path rels_path(parent.append("_rels").append(part.filename() + ".rels").string());
xml::serializer rels_serializer(rels_stream, rels_path.string());
path rels_path(parent.append("_rels").append(part.filename() + ".rels").string());
begin_part(rels_path);
const auto xmlns = xlnt::constants::get_namespace("relationships");
rels_serializer.start_element(xmlns, "Relationships");
rels_serializer.namespace_decl(xmlns, "");
serializer().start_element(xmlns, "Relationships");
serializer().namespace_decl(xmlns, "");
for (const auto &relationship : relationships)
{
rels_serializer.start_element(xmlns, "Relationship");
serializer().start_element(xmlns, "Relationship");
rels_serializer.attribute("Id", relationship.get_id());
rels_serializer.attribute("Type", relationship.get_type());
rels_serializer.attribute("Target", relationship.get_target().get_path().string());
serializer().attribute("Id", relationship.get_id());
serializer().attribute("Type", relationship.get_type());
serializer().attribute("Target", relationship.get_target().get_path().string());
if (relationship.get_target_mode() == xlnt::target_mode::external)
{
rels_serializer.attribute("TargetMode", "External");
serializer().attribute("TargetMode", "External");
}
rels_serializer.end_element(xmlns, "Relationship");
serializer().end_element(xmlns, "Relationship");
}
rels_serializer.end_element(xmlns, "Relationships");
destination_.write_string(rels_stream.str(), rels_path);
serializer().end_element(xmlns, "Relationships");
}

View File

@ -24,11 +24,15 @@
#include <cstdint>
#include <iostream>
#include <memory>
#include <vector>
#include <detail/include_libstudxml.hpp>
#include <xlnt/xlnt_config.hpp>
#include <xlnt/packaging/zip_file.hpp>
#include <detail/zip.hpp>
namespace Partio {
class ZipFileWriter;
}
namespace xml {
class serializer;
@ -48,17 +52,13 @@ namespace detail {
/// <summary>
/// Handles writing a workbook into an XLSX file.
/// </summary>
class XLNT_API xlsx_producer
class xlsx_producer
{
public:
xlsx_producer(const workbook &target);
void write(const path &destination);
void write(std::ostream &destination);
void write(std::vector<std::uint8_t> &destination);
private:
/// <summary>
/// Write all files needed to create a valid XLSX file which represents all
@ -66,6 +66,9 @@ private:
/// </summary>
void populate_archive();
void begin_part(const path &part);
void end_part();
// Package Parts
void write_content_types();
@ -134,18 +137,10 @@ private:
/// A reference to the workbook which is the object of read/write operations.
/// </summary>
const workbook &source_;
/// <summary>
/// A reference to the archive into which files representing the workbook
/// will be written.
/// </summary>
zip_file destination_;
/// <summary>
/// Instead of passing the current serializer into part serialization methods,
/// store pointer in this field and access it in methods with xlsx_producer::serializer().
/// </summary>
xml::serializer *serializer_;
Partio::ZipFileWriter *archive_;
std::unique_ptr<std::ostream> current_part_stream_;
std::unique_ptr<xml::serializer> current_part_serializer_;
};
} // namespace detail

502
source/detail/zip.cpp Normal file
View File

@ -0,0 +1,502 @@
/*
PARTIO SOFTWARE
Copyright 2010 Disney Enterprises, Inc. All rights reserved
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* 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.
* The names "Disney", "Walt Disney Pictures", "Walt Disney Animation
Studios" or the names of its contributors may NOT be used to
endorse or promote products derived from this software without
specific prior written permission from Walt Disney Pictures.
Disclaimer: THIS SOFTWARE IS PROVIDED BY WALT DISNEY PICTURES AND
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE, NONINFRINGEMENT AND TITLE ARE DISCLAIMED.
IN NO EVENT SHALL WALT DISNEY PICTURES, 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 BASED 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 DAMAGES.
*/
extern "C"{
#include <zlib.h>
}
#include <algorithm>
#include <cassert>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <stdexcept>
#include <cstring>
#include <string>
#include <detail/zip.hpp>
namespace Partio{
template<class T>
inline void Swap_Endianity(T& x)
{
assert(sizeof(T)<=8);
if(sizeof(T)>1) {
T old=x;
for(unsigned int k=1;k<=sizeof(T);k++) ((char*)&x)[k-1]=((char*)&old)[sizeof(T)-k];
}
}
template<class T>
inline void Read_Primitive(std::istream& stream,T& x)
{
stream.read(&(char&)x,sizeof(T));
}
template<class T>
inline void Write_Primitive(std::ostream& stream,const T& x)
{
stream.write(&(char&)x,sizeof(T));
}
//#####################################################################
// class ZipFileHeader
//#####################################################################
struct ZipFileHeader
{
unsigned short version;
unsigned short flags;
unsigned short compression_type;
unsigned short stamp_date,stamp_time;
unsigned int crc;
unsigned int compressed_size,uncompressed_size;
std::string filename;
unsigned int header_offset; // local header offset
ZipFileHeader()
{}
ZipFileHeader(const std::string& filename_input)
:version(20),flags(0),compression_type(8),stamp_date(0),stamp_time(0),crc(0),
compressed_size(0),uncompressed_size(0),filename(filename_input),header_offset(0)
{}
bool Read(std::istream& istream,const bool global)
{unsigned int sig;
unsigned short version,flags;
// read and check for local/global magic
if(global){
Read_Primitive(istream,sig);
if(sig!=0x02014b50){std::cerr<<"Did not find global header signature"<<std::endl;return false;}
Read_Primitive(istream,version);}
else{
Read_Primitive(istream,sig);
if(sig!=0x04034b50){std::cerr<<"Did not find local header signature"<<std::endl;return false;}}
// Read rest of header
Read_Primitive(istream,version);
Read_Primitive(istream,flags);
Read_Primitive(istream,compression_type);
Read_Primitive(istream,stamp_date);
Read_Primitive(istream,stamp_time);
Read_Primitive(istream,crc);
Read_Primitive(istream,compressed_size);
Read_Primitive(istream,uncompressed_size);
unsigned short filename_length,extra_length;
Read_Primitive(istream,filename_length);
Read_Primitive(istream,extra_length);
unsigned short comment_length=0;
if(global){
Read_Primitive(istream,comment_length); // filecomment
unsigned short disk_number_start,int_file_attrib;
unsigned int ext_file_attrib;
Read_Primitive(istream,disk_number_start); // disk# start
Read_Primitive(istream,int_file_attrib); // internal file
Read_Primitive(istream,ext_file_attrib); // ext final
Read_Primitive(istream,header_offset);} // rel offset
char* buf=new char[std::max(comment_length,std::max(filename_length,extra_length))+1];
istream.read(buf,filename_length);
buf[filename_length]=0;
filename=std::string(buf, buf + filename_length);
istream.read(buf,extra_length);
if(global) istream.read(buf,comment_length);
delete [] buf;
return true;}
void Write(std::ostream& ostream,const bool global) const
{if(global){
Write_Primitive(ostream,(unsigned int)0x02014b50); // header sig
Write_Primitive(ostream,(unsigned short)00);} // version made by
else Write_Primitive(ostream,(unsigned int)0x04034b50);
Write_Primitive(ostream,version);
Write_Primitive(ostream,flags);
Write_Primitive(ostream,compression_type);
Write_Primitive(ostream,stamp_date);
Write_Primitive(ostream,stamp_time);
Write_Primitive(ostream,crc);
Write_Primitive(ostream,compressed_size);
Write_Primitive(ostream,uncompressed_size);
Write_Primitive(ostream,(unsigned short)filename.length());
Write_Primitive(ostream,(unsigned short)0); // extra lengthx
if(global){
Write_Primitive(ostream,(unsigned short)0); // filecomment
Write_Primitive(ostream,(unsigned short)0); // disk# start
Write_Primitive(ostream,(unsigned short)0); // internal file
Write_Primitive(ostream,(unsigned int)0); // ext final
Write_Primitive(ostream,(unsigned int)header_offset);} // rel offset
for(unsigned int i=0;i<filename.length();i++) Write_Primitive(ostream,filename.c_str()[i]);}
//#####################################################################
};
//#####################################################################
// class ZipStreambufDecompress
//#####################################################################
class ZipStreambufDecompress:public std::streambuf
{
static const unsigned int buffer_size=512;
std::istream& istream;
z_stream strm;
unsigned char in[buffer_size],out[buffer_size];
ZipFileHeader header;
int total_read,total_uncompressed;
bool own_istream;
bool valid;
bool compressed_data;
static const unsigned short DEFLATE=8;
static const unsigned short UNCOMPRESSED=0;
public:
ZipStreambufDecompress(std::istream& stream,ZipFileHeader central_header)
:istream(stream),total_read(0),total_uncompressed(0),valid(true),header(central_header)
{
strm.zalloc=Z_NULL;strm.zfree=Z_NULL;strm.opaque=Z_NULL;strm.avail_in=0;strm.next_in=Z_NULL;
setg((char*)in,(char*)in,(char*)in);
setp(0,0);
// skip the header
valid=header.Read(istream,false);
if(header.compression_type==DEFLATE) compressed_data=true;
else if(header.compression_type==UNCOMPRESSED) compressed_data=false;
else{
compressed_data=false;std::cerr<<"ZIP: got unrecognized compressed data (Supported deflate/uncompressed)"<<std::endl;
valid=false;}
// initialize the inflate
if(compressed_data && valid){
int result=inflateInit2(&strm,-MAX_WBITS);
if(result!=Z_OK){std::cerr<<"gzip: inflateInit2 did not return Z_OK"<<std::endl;valid=false;}}
header = central_header;
}
virtual ~ZipStreambufDecompress()
{if(compressed_data && valid) inflateEnd(&strm);}
int process()
{if(!valid) return -1;
if(compressed_data){
strm.avail_out=buffer_size-4;
strm.next_out=(Bytef*)(out+4);
while(strm.avail_out!=0){
if(strm.avail_in==0){ // buffer empty, read some more from file
istream.read((char*)in,std::min((unsigned int)buffer_size,header.compressed_size-total_read));
strm.avail_in=istream.gcount();
total_read+=strm.avail_in;
strm.next_in=(Bytef*)in;}
int ret=inflate(&strm,Z_NO_FLUSH); // decompress
switch(ret){
case Z_STREAM_ERROR:
std::cerr<<"libz error Z_STREAM_ERROR"<<std::endl;
valid=false;return -1;
case Z_NEED_DICT:
case Z_DATA_ERROR:
case Z_MEM_ERROR:
std::cerr<<"gzip error "<<strm.msg<<std::endl;
valid=false;return -1;}
if(ret==Z_STREAM_END) break;}
int unzip_count=buffer_size-strm.avail_out-4;
total_uncompressed+=unzip_count;
return unzip_count;}
else{ // uncompressed, so just read
istream.read((char*)(out+4),std::min(buffer_size-4,header.uncompressed_size-total_read));
int count=istream.gcount();
total_read+=count;
return count;}
return 1;}
virtual int underflow()
{if(gptr() && (gptr()<egptr())) return traits_type::to_int_type(*gptr()); // if we already have data just use it
int put_back_count=gptr()-eback();
if(put_back_count>4) put_back_count=4;
std::memmove(out+(4-put_back_count),gptr()-put_back_count,put_back_count);
int num=process();
setg((char*)(out+4-put_back_count),(char*)(out+4),(char*)(out+4+num));
if(num<=0) return EOF;
return traits_type::to_int_type(*gptr());}
virtual int overflow(int c=EOF)
{assert(false);return EOF;}
//#####################################################################
};
//#####################################################################
// class ZipStreambufCompress
//#####################################################################
class ZipStreambufCompress:public std::streambuf
{
static const int buffer_size=512;
std::ostream& ostream; // owned when header==0 (when not part of zip file)
z_stream strm;
unsigned char in[buffer_size],out[buffer_size];
ZipFileHeader* header;
unsigned int header_offset;
unsigned int uncompressed_size;
unsigned int crc;
bool valid;
public:
ZipStreambufCompress(ZipFileHeader* header,std::ostream& stream)
:ostream(stream),header(header),valid(true)
{
strm.zalloc=Z_NULL;strm.zfree=Z_NULL;strm.opaque=Z_NULL;
int ret=deflateInit2(&strm,Z_DEFAULT_COMPRESSION,Z_DEFLATED,-MAX_WBITS,8,Z_DEFAULT_STRATEGY);
if(ret != Z_OK){std::cerr<<"libz: failed to deflateInit"<<std::endl;valid=false;return;}
setg(0,0,0);
setp((char*)in,(char*)(in+buffer_size-4)); // we want to be 4 aligned
// Write appropriate header
if(header){header->header_offset=stream.tellp();header->Write(ostream,false);}
uncompressed_size=crc=0;
}
virtual ~ZipStreambufCompress()
{if(valid){
process(true);
deflateEnd(&strm);
if(header){
std::ios::streampos final_position=ostream.tellp();
header->uncompressed_size=uncompressed_size;
header->crc=crc;
ostream.seekp(header->header_offset);
header->Write(ostream,false);
ostream.seekp(final_position);}
else{Write_Primitive(ostream,crc);Write_Primitive(ostream,uncompressed_size);}}
if(!header) delete &ostream;}
protected:
int process(bool flush)
{if(!valid) return -1;
strm.next_in=(Bytef*)pbase();
strm.avail_in=pptr()-pbase();
while(strm.avail_in!=0 || flush){
strm.avail_out=buffer_size;
strm.next_out=(Bytef*)out;
int ret=deflate(&strm,flush?Z_FINISH:Z_NO_FLUSH);
if(!(ret!=Z_BUF_ERROR && ret!=Z_STREAM_ERROR)){
valid=false;
std::cerr<<"gzip: gzip error "<<strm.msg<<std::endl;;
return -1;}
int generated_output=strm.next_out-(Bytef*)out;
ostream.write((char*)out,generated_output);
if(header) header->compressed_size+=generated_output;
if(ret==Z_STREAM_END) break;}
// update counts, crc's and buffers
int consumed_input=pptr()-pbase();
uncompressed_size+=consumed_input;
crc=crc32(crc,(Bytef*)in,consumed_input);
setp(pbase(),pbase()+buffer_size-4);return 1;}
virtual int sync()
{if(pptr() && pptr()>pbase()) return process(false);return 0;}
virtual int underflow()
{std::runtime_error("Attempt to read write only ostream");return 0;}
virtual int overflow(int c=EOF)
{if(c!=EOF){*pptr()=c;pbump(1);}
if(process(false)==EOF) return EOF;
return c;}
//#####################################################################
};
//#####################################################################
// Class ZIP_FILE_ISTREAM
//#####################################################################
// Class needed because istream cannot own its streambuf
class ZIP_FILE_ISTREAM:public std::istream
{
ZipStreambufDecompress buf;
public:
ZIP_FILE_ISTREAM(std::istream& istream,ZipFileHeader header)
:std::istream(&buf),buf(istream,header)
{}
virtual ~ZIP_FILE_ISTREAM()
{}
//#####################################################################
};
//#####################################################################
// Class ZIP_FILE_OSTREAM
//#####################################################################
// Class needed because ostream cannot own its streambuf
class ZIP_FILE_OSTREAM:public std::ostream
{
ZipStreambufCompress buf;
public:
ZIP_FILE_OSTREAM(ZipFileHeader* header,std::ostream& ostream)
:std::ostream(&buf),buf(header,ostream)
{}
virtual ~ZIP_FILE_OSTREAM()
{}
//#####################################################################
};
//#####################################################################
// Function ZipFileWriter
//#####################################################################
ZipFileWriter::
ZipFileWriter(std::ostream& stream) : ostream(stream)
{
if(!ostream) throw std::runtime_error("ZIP: Invalid file handle");
}
//#####################################################################
// Function ZipFileWriter
//#####################################################################
ZipFileWriter::
~ZipFileWriter()
{
// Write all file headers
std::ios::streampos final_position=ostream.tellp();
for(unsigned int i=0;i<files.size();i++){files[i]->Write(ostream,true);delete files[i];}
std::ios::streampos central_end=ostream.tellp();
// Write end of central
Write_Primitive(ostream,(unsigned int)0x06054b50); // end of central
Write_Primitive(ostream,(unsigned short)0); // this disk number
Write_Primitive(ostream,(unsigned short)0); // this disk number
Write_Primitive(ostream,(unsigned short)files.size()); // one entry in center in this disk
Write_Primitive(ostream,(unsigned short)files.size()); // one entry in center
Write_Primitive(ostream,(unsigned int)(central_end-final_position)); // size of header
Write_Primitive(ostream,(unsigned int)final_position); // offset to header
Write_Primitive(ostream,(unsigned short)0); // zip comment
}
//#####################################################################
// Function ZipFileWriter
//#####################################################################
std::ostream* ZipFileWriter::
Add_File(const std::string& filename,const bool binary)
{
files.push_back(new ZipFileHeader(filename));
return new ZIP_FILE_OSTREAM(files.back(),ostream);
}
//#####################################################################
// Function ZipFileReader
//#####################################################################
ZipFileReader::
ZipFileReader(std::istream &stream) : istream(stream)
{
if(!istream) throw std::runtime_error("ZIP: Invalid file handle");
Find_And_Read_Central_Header();
}
//#####################################################################
// Function ZipFileReader
//#####################################################################
ZipFileReader::
~ZipFileReader()
{
std::map<std::string,ZipFileHeader*>::iterator i=filename_to_header.begin();
for(;i!=filename_to_header.end();++i)
delete i->second;
}
//#####################################################################
// Function Find_And_Read_Central_Header
//#####################################################################
bool ZipFileReader::
Find_And_Read_Central_Header()
{
// Find the header
// NOTE: this assumes the zip file header is the last thing written to file...
istream.seekg(0,std::ios_base::end);
std::ios::streampos end_position=istream.tellg();
unsigned int max_comment_size=0xffff; // max size of header
unsigned int read_size_before_comment=22;
std::ios::streamoff read_start=max_comment_size+read_size_before_comment;
if(read_start>end_position) read_start=end_position;
istream.seekg(end_position-read_start);
char *buf=new char[read_start];
if(read_start<=0){std::cerr<<"ZIP: Invalid read buffer size"<<std::endl;return false;}
istream.read(buf,read_start);
int found=-1;
for(unsigned int i=0;i<read_start-3;i++){
if(buf[i]==0x50 && buf[i+1]==0x4b && buf[i+2]==0x05 && buf[i+3]==0x06){found=i;break;}}
delete [] buf;
if(found==-1){std::cerr<<"ZIP: Failed to find zip header"<<std::endl;return false;}
// seek to end of central header and read
istream.seekg(end_position-(read_start-found));
unsigned int word;
unsigned short disk_number1,disk_number2,num_files,num_files_this_disk;
Read_Primitive(istream,word); // end of central
Read_Primitive(istream,disk_number1); // this disk number
Read_Primitive(istream,disk_number2); // this disk number
if(disk_number1!=disk_number2 || disk_number1!=0){
std::cerr<<"ZIP: multiple disk zip files are not supported"<<std::endl;return false;}
Read_Primitive(istream,num_files); // one entry in center in this disk
Read_Primitive(istream,num_files_this_disk); // one entry in center
if(num_files != num_files_this_disk){
std::cerr<<"ZIP: multi disk zip files are not supported"<<std::endl;return false;}
unsigned int size_of_header,header_offset;
Read_Primitive(istream,size_of_header); // size of header
Read_Primitive(istream,header_offset); // offset to header
// go to header and read all file headers
istream.seekg(header_offset);
for(int i=0;i<num_files;i++){
ZipFileHeader* header=new ZipFileHeader;
bool valid=header->Read(istream,true);
if(valid) filename_to_header[header->filename]=header;}
return true;
}
//#####################################################################
// Function Get_File
//#####################################################################
std::istream* ZipFileReader::Get_File(const std::string& filename,const bool binary)
{
std::map<std::string,ZipFileHeader*>::iterator i=filename_to_header.find(filename);
if(i!=filename_to_header.end()){
ZipFileHeader* header=i->second;
istream.seekg((*header).header_offset);return new ZIP_FILE_ISTREAM(istream,*header);
}
return 0;
}
//#####################################################################
// Function Get_File_List
//#####################################################################
void ZipFileReader::Get_File_List(std::vector<std::string>& filenames) const
{
filenames.clear();
std::map<std::string,ZipFileHeader*>::const_iterator i=filename_to_header.begin();
for(;i!=filename_to_header.end();++i)
filenames.push_back(i->first);
}
//#####################################################################
// Function Has_File
//#####################################################################
bool ZipFileReader::Has_File(const std::string &filename) const
{
return filename_to_header.find(filename) != filename_to_header.end();
}
} // namespace Partio

88
source/detail/zip.hpp Normal file
View File

@ -0,0 +1,88 @@
/*
PARTIO SOFTWARE
Copyright 2010 Disney Enterprises, Inc. All rights reserved
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* 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.
* The names "Disney", "Walt Disney Pictures", "Walt Disney Animation
Studios" or the names of its contributors may NOT be used to
endorse or promote products derived from this software without
specific prior written permission from Walt Disney Pictures.
Disclaimer: THIS SOFTWARE IS PROVIDED BY WALT DISNEY PICTURES AND
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE, NONINFRINGEMENT AND TITLE ARE DISCLAIMED.
IN NO EVENT SHALL WALT DISNEY PICTURES, 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 BASED 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 DAMAGES.
*/
#ifndef __ZIP__
#define __ZIP__
#include <fstream>
#include <iostream>
#include <map>
#include <stdexcept>
#include <vector>
namespace Partio{
struct ZipFileHeader;
//#####################################################################
// Functions Gzip_Out/Gzip_In - Create streams that read/write .gz
//#####################################################################
std::istream* Gzip_In(const std::string& filename,std::ios::openmode mode);
std::ostream* Gzip_Out(const std::string& filename,std::ios::openmode mode);
//#####################################################################
// Class ZipFileWriter
//#####################################################################
class ZipFileWriter
{
std::ostream &ostream;
std::vector<ZipFileHeader*> files;
public:
//#####################################################################
ZipFileWriter(std::ostream &filename);
virtual ~ZipFileWriter();
std::ostream* Add_File(const std::string& filename,const bool binary=true);
//#####################################################################
};
//#####################################################################
// Class ZipFileReader
//#####################################################################
class ZipFileReader
{
std::istream &istream;
public:
std::map<std::string,ZipFileHeader*> filename_to_header;
//#####################################################################
ZipFileReader(std::istream &stream);
virtual ~ZipFileReader();
std::istream* Get_File(const std::string& filename,const bool binary=true);
void Get_File_List(std::vector<std::string>& filenames) const;
bool Has_File(const std::string &filename) const;
private:
bool Find_And_Read_Central_Header();
//#####################################################################
};
}
#endif

View File

@ -1,226 +0,0 @@
#include <cassert>
#include <fstream>
#include <iostream>
#include <cxxtest/TestSuite.h>
#include <xlnt/xlnt.hpp>
#include "helpers/path_helper.hpp"
#include "helpers/temporary_file.hpp"
class test_zip_file : public CxxTest::TestSuite
{
public:
test_zip_file()
{
existing_file = path_helper::get_data_directory("4_not-package.xlsx");
expected_string = "not-empty";
}
bool files_equal(const xlnt::path &left, const xlnt::path &right)
{
if(left.string() == right.string())
{
return true;
}
std::ifstream stream_left(left.string(), std::ios::binary);
std::ifstream stream_right(right.string(), std::ios::binary);
while(stream_left && stream_right)
{
if(stream_left.get() != stream_right.get())
{
return false;
}
}
return true;
}
void test_load_file()
{
temporary_file temp_file;
xlnt::zip_file f(existing_file);
f.save(temp_file.get_path());
TS_ASSERT(files_equal(existing_file, temp_file.get_path()));
}
void test_load_stream()
{
temporary_file temp;
std::ifstream in_stream(existing_file.string(), std::ios::binary);
xlnt::zip_file f(in_stream);
std::ofstream out_stream(temp.get_path().string(), std::ios::binary);
f.save(out_stream);
out_stream.close();
TS_ASSERT(files_equal(existing_file, temp.get_path()));
}
void test_load_bytes()
{
temporary_file temp_file;
std::vector<std::uint8_t> source_bytes;
std::ifstream in_stream(existing_file.string(), std::ios::binary);
while(in_stream)
{
source_bytes.push_back(static_cast<std::uint8_t>(in_stream.get()));
}
xlnt::zip_file f(source_bytes);
f.save(temp_file.get_path());
xlnt::zip_file f2;
f2.load(temp_file.get_path());
std::vector<std::uint8_t> result_bytes;
f2.save(result_bytes);
TS_ASSERT(source_bytes == result_bytes);
}
void test_reset()
{
xlnt::zip_file f(existing_file);
TS_ASSERT(!f.namelist().empty());
try
{
f.read(xlnt::path("text.txt"));
}
catch(std::exception e)
{
TS_ASSERT(false);
}
f.reset();
TS_ASSERT(f.namelist().empty());
try
{
f.read(xlnt::path("doesnt-exist.txt"));
TS_ASSERT(false);
}
catch(std::exception e)
{
}
f.write_string("b", xlnt::path("a"));
f.reset();
TS_ASSERT(f.namelist().empty());
f.write_string("b", xlnt::path("a"));
TS_ASSERT_DIFFERS(f.getinfo(xlnt::path("a")).file_size, 0);
}
void test_getinfo()
{
xlnt::zip_file f(existing_file);
auto info = f.getinfo(xlnt::path("text.txt"));
TS_ASSERT(info.filename.string() == "text.txt");
}
void test_infolist()
{
xlnt::zip_file f(existing_file);
TS_ASSERT_EQUALS(f.infolist().size(), 1);
}
void test_namelist()
{
xlnt::zip_file f(existing_file);
TS_ASSERT_EQUALS(f.namelist().size(), 1);
}
void test_open_by_name()
{
xlnt::zip_file f(existing_file);
std::stringstream ss;
ss << f.open(xlnt::path("text.txt")).rdbuf();
std::string result = ss.str();
TS_ASSERT(result == expected_string);
}
void test_open_by_info()
{
xlnt::zip_file f(existing_file);
std::stringstream ss;
ss << f.open(xlnt::path("text.txt")).rdbuf();
std::string result = ss.str();
TS_ASSERT(result == expected_string);
}
void test_read()
{
xlnt::zip_file f(existing_file);
TS_ASSERT(f.read(xlnt::path("text.txt")) == expected_string);
TS_ASSERT(f.read(f.getinfo(xlnt::path("text.txt"))) == expected_string);
}
void test_testzip()
{
xlnt::zip_file f(existing_file);
TS_ASSERT(!f.check_crc());
}
void test_write_file()
{
temporary_file temp_file;
xlnt::zip_file f;
auto text_file = path_helper::get_data_directory("2_text.xlsx");
f.write_file(text_file);
f.write_file(text_file, xlnt::path("a.txt"));
f.save(temp_file.get_path());
xlnt::zip_file f2(temp_file.get_path());
for(auto &info : f2.infolist())
{
TS_ASSERT(f2.read(info) == expected_string);
}
}
void test_write_string()
{
xlnt::zip_file f;
f.write_string("a\na", xlnt::path("a.txt"));
xlnt::zip_info info;
info.filename = xlnt::path("b.txt");
info.date_time.year = 2014;
f.write_string("b\nb", info);
temporary_file temp_file;
f.save(temp_file.get_path());
xlnt::zip_file f2(temp_file.get_path());
TS_ASSERT(f2.read(xlnt::path("a.txt")) == "a\na");
TS_ASSERT(f2.read(f2.getinfo(xlnt::path("b.txt"))) == "b\nb");
}
void test_comment()
{
xlnt::zip_file f;
f.comment = "comment";
temporary_file temp_file;
f.save(temp_file.get_path());
xlnt::zip_file f2(temp_file.get_path());
TS_ASSERT(f2.comment == "comment");
xlnt::zip_file f3;
std::vector<std::uint8_t> bytes { 1, 2, 3 };
TS_ASSERT_THROWS(f3.load(bytes), xlnt::invalid_file);
}
private:
xlnt::path existing_file;
std::string expected_string;
};

View File

@ -1,627 +0,0 @@
// Copyright (c) 2014-2016 Thomas Fussell
// Copyright (c) 2010-2015 openpyxl
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, WRISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE
//
// @license: http://www.opensource.org/licenses/mit-license.php
// @author: see AUTHORS file
#include <algorithm>
#include <cassert>
#include <cstring>
#include <fstream>
#include <iterator>
#include <miniz.h>
#include <xlnt/packaging/zip_file.hpp>
#include <xlnt/utils/path.hpp>
#include <xlnt/utils/exceptions.hpp>
namespace {
uint32_t crc32buf(const char *buf, std::size_t len)
{
uint32_t oldcrc32 = 0xFFFFFFFF;
uint32_t crc_32_tab[] = {
/* CRC polynomial 0xedb88320 */
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832,
0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a,
0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3,
0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab,
0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4,
0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074,
0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525,
0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615,
0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76,
0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6,
0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7,
0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7,
0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278,
0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330,
0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};
#define UPDC32(octet, crc) (crc_32_tab[((crc) ^ static_cast<uint8_t>(octet)) & 0xff] ^ ((crc) >> 8))
for (; len; --len, ++buf)
{
oldcrc32 = UPDC32(*buf, oldcrc32);
}
return ~oldcrc32;
}
tm safe_localtime(const time_t &t)
{
#ifdef _WIN32
tm time;
localtime_s(&time, &t);
return time;
#else
tm *time = localtime(&t);
assert(time != nullptr);
return *time;
#endif
}
std::size_t write_callback(void *opaque, mz_uint64 file_ofs, const void *pBuf, std::size_t n)
{
auto buffer = static_cast<std::vector<char> *>(opaque);
if (file_ofs + n > buffer->size())
{
auto new_size = static_cast<std::vector<char>::size_type>(file_ofs + n);
buffer->resize(new_size);
}
for (std::size_t i = 0; i < n; i++)
{
(*buffer)[static_cast<std::size_t>(file_ofs + i)] = (static_cast<const char *>(pBuf))[i];
}
return n;
}
} // namespace
namespace xlnt {
zip_info::zip_info()
: create_system(0),
create_version(0),
extract_version(0),
flag_bits(0),
volume(0),
internal_attr(0),
external_attr(0),
header_offset(0),
crc(0),
compress_size(0),
file_size(0)
{
date_time.year = 1980;
date_time.month = 0;
date_time.day = 0;
date_time.hours = 0;
date_time.minutes = 0;
date_time.seconds = 0;
}
zip_file::zip_file() : archive_(new mz_zip_archive())
{
reset();
}
zip_file::zip_file(const path &filename) : zip_file()
{
load(filename);
}
zip_file::zip_file(std::istream &stream) : zip_file()
{
load(stream);
}
zip_file::zip_file(const std::vector<std::uint8_t> &bytes) : zip_file()
{
load(bytes);
}
zip_file::~zip_file()
{
reset();
}
void zip_file::load(std::istream &stream)
{
if (!stream.good())
{
throw invalid_file("((std::istream))");
}
reset();
buffer_.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>());
if (buffer_.empty())
{
throw invalid_file("((stream)) - empty file");
}
remove_comment();
start_read();
}
void zip_file::load(const path &filename)
{
filename_ = filename;
std::ifstream stream(filename.string(), std::ios::binary);
if (!stream.good())
{
throw invalid_file(filename.string());
}
reset();
buffer_.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>());
if (buffer_.empty())
{
throw invalid_file(filename.string() + " - empty file");
}
remove_comment();
start_read();
}
void zip_file::load(const std::vector<std::uint8_t> &bytes)
{
if (bytes.empty())
{
throw invalid_file("((bytes))");
}
reset();
buffer_.assign(bytes.begin(), bytes.end());
remove_comment();
start_read();
}
void zip_file::save(const path &filename)
{
filename_ = filename;
std::ofstream stream(filename.string(), std::ios::binary);
save(stream);
}
void zip_file::save(std::ostream &stream)
{
if (archive_->m_zip_mode == MZ_ZIP_MODE_WRITING)
{
mz_zip_writer_finalize_archive(archive_.get());
}
if (archive_->m_zip_mode == MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED)
{
mz_zip_writer_end(archive_.get());
}
if (archive_->m_zip_mode == MZ_ZIP_MODE_INVALID)
{
start_read();
}
append_comment();
stream.write(buffer_.data(), static_cast<long>(buffer_.size()));
}
void zip_file::save(std::vector<std::uint8_t> &bytes)
{
if (archive_->m_zip_mode == MZ_ZIP_MODE_WRITING)
{
mz_zip_writer_finalize_archive(archive_.get());
}
if (archive_->m_zip_mode == MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED)
{
mz_zip_writer_end(archive_.get());
}
if (archive_->m_zip_mode == MZ_ZIP_MODE_INVALID)
{
start_read();
}
append_comment();
bytes.assign(buffer_.begin(), buffer_.end());
}
void zip_file::append_comment()
{
if (!comment.empty())
{
auto comment_length = std::min(static_cast<uint16_t>(comment.length()), std::numeric_limits<uint16_t>::max());
buffer_[buffer_.size() - 2] = static_cast<char>(comment_length);
buffer_[buffer_.size() - 1] = static_cast<char>(comment_length >> 8);
std::copy(comment.begin(), comment.end(), std::back_inserter(buffer_));
}
}
void zip_file::remove_comment()
{
if (buffer_.empty()) return;
std::size_t position = buffer_.size() - 1;
for (; position >= 3; position--)
{
if (buffer_[position - 3] == 'P' && buffer_[position - 2] == 'K' && buffer_[position - 1] == '\x05' &&
buffer_[position] == '\x06')
{
position = position + 17;
break;
}
}
if (position <= 3)
{
throw invalid_file("not a zip file");
}
uint16_t length = static_cast<uint16_t>(buffer_[position + 1]);
length = static_cast<uint16_t>(length << 8) + static_cast<uint16_t>(buffer_[position]);
position += 2;
if (length != 0)
{
comment = std::string(buffer_.data() + position, buffer_.data() + position + length);
buffer_.resize(buffer_.size() - length);
buffer_[buffer_.size() - 1] = 0;
buffer_[buffer_.size() - 2] = 0;
}
}
void zip_file::reset()
{
switch (archive_->m_zip_mode)
{
case MZ_ZIP_MODE_READING:
mz_zip_reader_end(archive_.get());
break;
case MZ_ZIP_MODE_WRITING:
mz_zip_writer_finalize_archive(archive_.get());
mz_zip_writer_end(archive_.get());
break;
default:
break;
}
buffer_.clear();
comment.clear();
start_write();
mz_zip_writer_finalize_archive(archive_.get());
mz_zip_writer_end(archive_.get());
}
zip_info zip_file::getinfo(const path &name)
{
if (archive_->m_zip_mode != MZ_ZIP_MODE_READING)
{
start_read();
}
int index = mz_zip_reader_locate_file(archive_.get(), name.string().c_str(), nullptr, 0);
if (index == -1)
{
throw std::runtime_error("not found");
}
return getinfo(index);
}
zip_info zip_file::getinfo(int index)
{
if (archive_->m_zip_mode != MZ_ZIP_MODE_READING)
{
start_read();
}
mz_zip_archive_file_stat stat;
mz_zip_reader_file_stat(archive_.get(), static_cast<mz_uint>(index), &stat);
zip_info result;
result.filename = path(std::string(stat.m_filename, stat.m_filename + std::strlen(stat.m_filename)));
result.comment = std::string(stat.m_comment, stat.m_comment + stat.m_comment_size);
result.compress_size = static_cast<std::size_t>(stat.m_comp_size);
result.file_size = static_cast<std::size_t>(stat.m_uncomp_size);
result.header_offset = static_cast<std::size_t>(stat.m_local_header_ofs);
result.crc = stat.m_crc32;
auto time = safe_localtime(stat.m_time);
result.date_time.year = 1900 + time.tm_year;
result.date_time.month = 1 + time.tm_mon;
result.date_time.day = time.tm_mday;
result.date_time.hours = time.tm_hour;
result.date_time.minutes = time.tm_min;
result.date_time.seconds = time.tm_sec;
result.flag_bits = stat.m_bit_flag;
result.internal_attr = stat.m_internal_attr;
result.external_attr = stat.m_external_attr;
result.extract_version = stat.m_version_needed;
result.create_version = stat.m_version_made_by;
result.volume = stat.m_file_index;
result.create_system = stat.m_method;
return result;
}
void zip_file::start_read()
{
if (archive_->m_zip_mode == MZ_ZIP_MODE_READING) return;
if (archive_->m_zip_mode == MZ_ZIP_MODE_WRITING)
{
mz_zip_writer_finalize_archive(archive_.get());
}
if (archive_->m_zip_mode == MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED)
{
mz_zip_writer_end(archive_.get());
}
if (!mz_zip_reader_init_mem(archive_.get(), buffer_.data(), buffer_.size(), 0))
{
throw std::runtime_error("bad zip");
}
}
void zip_file::start_write()
{
if (archive_->m_zip_mode == MZ_ZIP_MODE_WRITING) return;
switch (archive_->m_zip_mode)
{
case MZ_ZIP_MODE_READING:
{
mz_zip_archive archive_copy;
std::memset(&archive_copy, 0, sizeof(mz_zip_archive));
std::vector<char> buffer_copy(buffer_.begin(), buffer_.end());
if (!mz_zip_reader_init_mem(&archive_copy, buffer_copy.data(), buffer_copy.size(), 0))
{
throw std::runtime_error("bad zip");
}
mz_zip_reader_end(archive_.get());
archive_->m_pWrite = &write_callback;
archive_->m_pIO_opaque = &buffer_;
buffer_ = std::vector<char>();
if (!mz_zip_writer_init(archive_.get(), 0))
{
throw std::runtime_error("bad zip");
}
for (unsigned int i = 0; i < static_cast<unsigned int>(archive_copy.m_total_files); i++)
{
if (!mz_zip_writer_add_from_zip_reader(archive_.get(), &archive_copy, i))
{
throw std::runtime_error("fail");
}
}
mz_zip_reader_end(&archive_copy);
return;
}
default:
break;
}
archive_->m_pWrite = &write_callback;
archive_->m_pIO_opaque = &buffer_;
mz_zip_writer_init(archive_.get(), 0);
}
void zip_file::write_file(const path &filename)
{
path arcname(filename);
if (filename.is_absolute())
{
auto split = filename.split();
auto iter = split.begin() + 1;
arcname = path();
while (iter != split.end())
{
arcname.append(*iter++);
}
}
write_file(filename, arcname);
}
void zip_file::write_file(const path &filename, const path &arcname)
{
std::fstream file(filename.string(), std::ios::binary | std::ios::in);
std::stringstream ss;
ss << file.rdbuf();
write_string(ss.str(), arcname);
}
void zip_file::write_string(const std::string &bytes, const path &arcname)
{
if (archive_->m_zip_mode != MZ_ZIP_MODE_WRITING)
{
start_write();
}
mz_zip_writer_add_mem(archive_.get(), arcname.string().c_str(),
bytes.data(), bytes.size(), MZ_BEST_COMPRESSION);
}
void zip_file::write_string(const std::string &bytes, const zip_info &info)
{
if (info.filename.string().empty() || info.date_time.year < 1980)
{
throw std::runtime_error("must specify a filename and valid date (year >= 1980");
}
if (archive_->m_zip_mode != MZ_ZIP_MODE_WRITING)
{
start_write();
}
auto crc = crc32buf(bytes.c_str(), bytes.size());
mz_zip_writer_add_mem_ex(archive_.get(), info.filename.string().c_str(), bytes.data(), bytes.size(),
info.comment.c_str(), static_cast<mz_uint16>(info.comment.size()),
MZ_BEST_COMPRESSION, 0, crc);
}
std::string zip_file::read(const zip_info &info)
{
std::size_t size;
void *data_raw = mz_zip_reader_extract_file_to_heap(archive_.get(),
info.filename.string().c_str(), &size, 0);
if (data_raw == nullptr)
{
throw std::runtime_error("file couldn't be read");
}
auto data = static_cast<char *>(data_raw);
std::string extracted(data, data + size);
mz_free(data);
return extracted;
}
std::string zip_file::read(const path &name)
{
return read(getinfo(name));
}
bool zip_file::has_file(const path &name)
{
if (archive_->m_zip_mode != MZ_ZIP_MODE_READING)
{
start_read();
}
int index = mz_zip_reader_locate_file(archive_.get(), name.string().c_str(), nullptr, 0);
return index != -1;
}
bool zip_file::has_file(const zip_info &name)
{
return has_file(name.filename);
}
std::vector<zip_info> zip_file::infolist()
{
if (archive_->m_zip_mode != MZ_ZIP_MODE_READING)
{
start_read();
}
std::vector<zip_info> info;
for (std::size_t i = 0; i < mz_zip_reader_get_num_files(archive_.get()); i++)
{
info.push_back(getinfo(static_cast<int>(i)));
}
return info;
}
std::vector<path> zip_file::namelist()
{
std::vector<path> names;
for (auto &info : infolist())
{
names.push_back(info.filename);
}
return names;
}
std::ostream &zip_file::open(const path &name)
{
return open(getinfo(name));
}
std::ostream &zip_file::open(const zip_info &name)
{
auto data = read(name);
std::string data_string(data.begin(), data.end());
open_stream_ << data_string;
return open_stream_;
}
bool zip_file::check_crc()
{
if (archive_->m_zip_mode == MZ_ZIP_MODE_INVALID)
{
throw std::runtime_error("not open");
}
for (auto &file : infolist())
{
auto content = read(file);
auto crc = crc32buf(content.c_str(), content.size());
if (crc != file.crc)
{
return true;
}
}
return false;
}
path zip_file::get_filename() const
{
return filename_;
}
} // namespace xlnt

View File

@ -7,47 +7,61 @@
#include <helpers/path_helper.hpp>
#include <xlnt/workbook/workbook.hpp>
#define TEST_CRYPTO true
#ifndef TEST_CRYPTO
#define TEST_CRYPTO false
#endif
class test_consume_xlsx : public CxxTest::TestSuite
{
public:
void test_decrypt_agile()
{
xlnt::workbook wb;
wb.load(path_helper::get_data_directory("14_encrypted_excel_2016.xlsx"), "secret");
}
void test_decrypt_agile()
{
xlnt::workbook wb;
#if TEST_CRYPTO
wb.load(path_helper::get_data_directory("14_encrypted_excel_2016.xlsx"), "secret");
#endif
}
void test_decrypt_libre_office()
{
xlnt::workbook wb;
wb.load(path_helper::get_data_directory("15_encrypted_libre_office.xlsx"), "secret");
}
void test_decrypt_libre_office()
{
xlnt::workbook wb;
#if TEST_CRYPTO
wb.load(path_helper::get_data_directory("15_encrypted_libre_office.xlsx"), "secret");
#endif
}
void test_decrypt_standard()
{
xlnt::workbook wb;
wb.load(path_helper::get_data_directory("16_encrypted_excel_2007.xlsx"), "password");
}
void test_decrypt_standard()
{
xlnt::workbook wb;
#if TEST_CRYPTO
wb.load(path_helper::get_data_directory("16_encrypted_excel_2007.xlsx"), "password");
#endif
}
void test_decrypt_numbers()
{
{
TS_SKIP("");
xlnt::workbook wb;
wb.load(path_helper::get_data_directory("17_encrypted_numbers.xlsx"), "secret");
}
xlnt::workbook wb;
#if TEST_CRYPTO
wb.load(path_helper::get_data_directory("17_encrypted_numbers.xlsx"), "secret");
#endif
}
void test_comments()
{
xlnt::workbook wb;
wb.load("data/18_basic_comments.xlsx");
xlnt::workbook wb;
wb.load("data/18_basic_comments.xlsx");
auto sheet1 = wb[0];
TS_ASSERT_EQUALS(sheet1.get_cell("A1").get_value<std::string>(), "Sheet1!A1");
TS_ASSERT_EQUALS(sheet1.get_cell("A1").comment().plain_text(), "Sheet1 comment");
TS_ASSERT_EQUALS(sheet1.get_cell("A1").comment().author(), "Microsoft Office User");
auto sheet1 = wb[0];
TS_ASSERT_EQUALS(sheet1.get_cell("A1").get_value<std::string>(), "Sheet1!A1");
TS_ASSERT_EQUALS(sheet1.get_cell("A1").comment().plain_text(), "Sheet1 comment");
TS_ASSERT_EQUALS(sheet1.get_cell("A1").comment().author(), "Microsoft Office User");
auto sheet2 = wb[1];
TS_ASSERT_EQUALS(sheet2.get_cell("A1").get_value<std::string>(), "Sheet2!A1");
TS_ASSERT_EQUALS(sheet2.get_cell("A1").comment().plain_text(), "Sheet2 comment");
TS_ASSERT_EQUALS(sheet2.get_cell("A1").comment().author(), "Microsoft Office User");
auto sheet2 = wb[1];
TS_ASSERT_EQUALS(sheet2.get_cell("A1").get_value<std::string>(), "Sheet2!A1");
TS_ASSERT_EQUALS(sheet2.get_cell("A1").comment().plain_text(), "Sheet2 comment");
TS_ASSERT_EQUALS(sheet2.get_cell("A1").comment().author(), "Microsoft Office User");
}
};

View File

@ -3,10 +3,10 @@
#include <iostream>
#include <cxxtest/TestSuite.h>
#include <detail/vector_streambuf.hpp>
#include <helpers/temporary_file.hpp>
#include <helpers/path_helper.hpp>
#include <helpers/xml_helper.hpp>
#include <xlnt/packaging/zip_file.hpp>
#include <xlnt/workbook/workbook.hpp>
class test_produce_xlsx : public CxxTest::TestSuite
@ -14,13 +14,19 @@ class test_produce_xlsx : public CxxTest::TestSuite
public:
bool workbook_matches_file(xlnt::workbook &wb, const xlnt::path &file)
{
std::vector<std::uint8_t> buffer;
wb.save(buffer);
std::vector<std::uint8_t> wb_data;
wb.save(wb_data);
xlnt::zip_file wb_archive(buffer);
xlnt::zip_file file_archive(file);
std::ifstream file_stream(file.string(), std::ios::binary);
std::vector<std::uint8_t> file_data;
return xml_helper::xlsx_archives_match(wb_archive, file_archive);
{
xlnt::detail::vector_ostreambuf file_data_buffer(file_data);
std::ostream file_data_stream(&file_data_buffer);
file_data_stream << file_stream.rdbuf();
}
return xml_helper::xlsx_archives_match(wb_data, file_data);
}
void test_produce_minimal()
@ -35,8 +41,9 @@ public:
TS_ASSERT(workbook_matches_file(wb, path_helper::get_data_directory("9_default-excel.xlsx")));
}
void _test_produce_default_libre_office()
void test_produce_default_libre_office()
{
TS_SKIP("");
xlnt::workbook wb = xlnt::workbook::empty_libre_office();
TS_ASSERT(workbook_matches_file(wb, path_helper::get_data_directory("10_default-libre-office.xlsx")));
}

View File

@ -4,9 +4,9 @@
#include <iostream>
#include <cxxtest/TestSuite.h>
#include <detail/vector_streambuf.hpp>
#include <helpers/path_helper.hpp>
#include <helpers/xml_helper.hpp>
#include <xlnt/packaging/zip_file.hpp>
#include <xlnt/workbook/workbook.hpp>
class test_round_trip : public CxxTest::TestSuite
@ -18,22 +18,16 @@ public:
/// </summary>
bool round_trip_matches_wrw(const xlnt::workbook &original)
{
std::vector<std::uint8_t> buffer;
original.save(buffer);
xlnt::zip_file original_archive;
original_archive.load(buffer);
std::vector<std::uint8_t> original_buffer;
original.save(original_buffer);
xlnt::workbook resulting_workbook;
resulting_workbook.load(buffer);
resulting_workbook.load(original_buffer);
buffer.clear();
resulting_workbook.save(buffer);
xlnt::zip_file resulting_archive;
resulting_archive.load(buffer);
std::vector<std::uint8_t> resulting_buffer;
resulting_workbook.save(resulting_buffer);
return xml_helper::xlsx_archives_match(original_archive, resulting_archive);
return xml_helper::xlsx_archives_match(original_buffer, resulting_buffer);
}
/// <summary>
@ -42,8 +36,14 @@ public:
/// </summary>
bool round_trip_matches_rw(const xlnt::path &original)
{
xlnt::zip_file original_archive;
original_archive.load(original);
std::ifstream file_stream(original.string(), std::ios::binary);
std::vector<std::uint8_t> original_data;
{
xlnt::detail::vector_ostreambuf file_data_buffer(original_data);
std::ostream file_data_stream(&file_data_buffer);
file_data_stream << file_stream.rdbuf();
}
xlnt::workbook original_workbook;
original_workbook.load(original);
@ -51,10 +51,7 @@ public:
std::vector<std::uint8_t> buffer;
original_workbook.save(buffer);
xlnt::zip_file resulting_archive;
resulting_archive.load(buffer);
return xml_helper::xlsx_archives_match(original_archive, resulting_archive);
return xml_helper::xlsx_archives_match(original_data, buffer);
}
void test_round_trip_minimal_wrw()
@ -69,14 +66,14 @@ public:
TS_ASSERT(round_trip_matches_wrw(wb));
}
void _test_round_trip_empty_libre_office_wrw()
void test_round_trip_empty_libre_office_wrw()
{
TS_SKIP("");
xlnt::workbook wb = xlnt::workbook::empty_libre_office();
TS_ASSERT(round_trip_matches_wrw(wb));
}
void _test_round_trip_empty_pages_wrw()
void test_round_trip_empty_pages_wrw()
{
TS_SKIP("");
xlnt::workbook wb = xlnt::workbook::empty_numbers();

View File

@ -26,19 +26,18 @@
#include <fstream>
#include <functional>
#include <set>
#include <sstream>
#include <detail/cell_impl.hpp>
#include <detail/constants.hpp>
#include <detail/excel_thumbnail.hpp>
#include <detail/xlsx_consumer.hpp>
#include <detail/xlsx_producer.hpp>
#include <detail/vector_streambuf.hpp>
#include <detail/workbook_impl.hpp>
#include <detail/worksheet_impl.hpp>
#include <detail/xlsx_consumer.hpp>
#include <detail/xlsx_producer.hpp>
#include <xlnt/cell/cell.hpp>
#include <xlnt/packaging/manifest.hpp>
#include <xlnt/packaging/relationship.hpp>
#include <xlnt/packaging/zip_file.hpp>
#include <xlnt/styles/alignment.hpp>
#include <xlnt/styles/border.hpp>
#include <xlnt/styles/format.hpp>
@ -689,7 +688,9 @@ void workbook::load(const std::vector<std::uint8_t> &data)
{
clear();
detail::xlsx_consumer consumer(*this);
consumer.read(data);
xlnt::detail::vector_istreambuf data_buffer(data);
std::istream data_stream(&data_buffer);
consumer.read(data_stream);
}
void workbook::load(const std::string &filename)
@ -701,7 +702,8 @@ void workbook::load(const path &filename)
{
clear();
detail::xlsx_consumer consumer(*this);
consumer.read(filename);
std::ifstream file_stream(filename.string(), std::ios::binary);
consumer.read(file_stream);
}
void workbook::load(const std::string &filename, const std::string &password)
@ -719,7 +721,9 @@ void workbook::load(const std::vector<std::uint8_t> &data, const std::string &pa
{
clear();
detail::xlsx_consumer consumer(*this);
consumer.read(data, password);
xlnt::detail::vector_istreambuf data_buffer(data);
std::istream data_stream(&data_buffer);
consumer.read(data_stream, password);
}
void workbook::load(std::istream &stream, const std::string &password)
@ -731,8 +735,9 @@ void workbook::load(std::istream &stream, const std::string &password)
void workbook::save(std::vector<std::uint8_t> &data) const
{
detail::xlsx_producer producer(*this);
producer.write(data);
xlnt::detail::vector_ostreambuf data_buffer(data);
std::ostream data_stream(&data_buffer);
save(data_stream);
}
void workbook::save(const std::string &filename) const
@ -743,7 +748,8 @@ void workbook::save(const std::string &filename) const
void workbook::save(const path &filename) const
{
detail::xlsx_producer producer(*this);
producer.write(filename);
std::ofstream file_stream(filename.string(), std::ios::binary);
save(file_stream);
}
void workbook::save(std::ostream &stream) const

View File

@ -4,6 +4,7 @@ project(${LIBRARY_NAME}.test VERSION ${LIBRARY_VERSION} LANGUAGES CXX C)
if(NOT COMBINED_PROJECT)
add_subdirectory(${LIBRARY_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}/source)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../third-party ${CMAKE_CURRENT_BINARY_DIR}/third-party)
endif()
include_directories(${LIBRARY_INCLUDE_DIR})
@ -12,6 +13,7 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR})
include_directories(${THIRD_PARTY_DIR}/cxxtest)
include_directories(${THIRD_PARTY_DIR}/utfcpp/source)
include_directories(${THIRD_PARTY_DIR}/pugixml/src)
include_directories(${THIRD_PARTY_DIR}/zlib)
file(GLOB CELL_TESTS ${LIBRARY_SOURCE_DIR}/cell/tests/test_*.hpp)
file(GLOB CHARTS_TESTS ${LIBRARY_SOURCE_DIR}/charts/tests/test_*.hpp)
@ -24,6 +26,8 @@ file(GLOB UTILS_TESTS ${LIBRARY_SOURCE_DIR}/utils/tests/test_*.hpp)
file(GLOB WORKBOOK_TESTS ${LIBRARY_SOURCE_DIR}/workbook/tests/test_*.hpp)
file(GLOB WORKSHEET_TESTS ${LIBRARY_SOURCE_DIR}/worksheet/tests/test_*.hpp)
set(ZIP ${LIBRARY_SOURCE_DIR}/detail/zip.cpp)
set(TESTS ${CELL_TESTS} ${CHARTS_TESTS} ${CHARTSHEET_TESTS} ${DRAWING_TESTS}
${FORMULA_TESTS} ${PACKAGING_TESTS} ${STYLES_TESTS} ${UTILS_TESTS}
${WORKBOOK_TESTS} ${WORKSHEET_TESTS})
@ -38,7 +42,7 @@ file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/tests")
set(RUNNER "${CMAKE_CURRENT_BINARY_DIR}/runner-autogen.cpp")
set_source_files_properties(${RUNNER} PROPERTIES GENERATED TRUE)
add_executable(${PROJECT_NAME} ${TEST_HELPERS} ${TESTS} ${RUNNER} ${PUGIXML})
add_executable(${PROJECT_NAME} ${TEST_HELPERS} ${TESTS} ${RUNNER} ${PUGIXML} $<TARGET_OBJECTS:xlnt.third-party> ${ZIP})
source_group(helpers FILES ${TEST_HELPERS})
source_group(tests\\cell FILES ${CELL_TESTS})

View File

@ -1,8 +1,9 @@
#pragma once
#include <pugixml.hpp>
#include <sstream>
#include <pugixml.hpp>
#include <detail/zip.hpp>
#include <helpers/path_helper.hpp>
#include <xlnt/packaging/manifest.hpp>
@ -20,7 +21,7 @@ public:
child_order_differs,
equivalent,
};
struct comparison_result
{
difference_type difference;
@ -274,8 +275,8 @@ public:
{
std::vector<std::uint8_t> bytes;
wb.save(bytes);
xlnt::zip_file archive;
archive.load(bytes);
std::istringstream file_stream(std::string(bytes.begin(), bytes.end()));
Partio::ZipFileReader archive(file_stream);
return string_matches_archive_member(expected, archive, part, content_type);
}
@ -285,27 +286,34 @@ public:
{
std::vector<std::uint8_t> bytes;
wb.save(bytes);
xlnt::zip_file archive;
archive.load(bytes);
std::istringstream file_stream(std::string(bytes.begin(), bytes.end()));
Partio::ZipFileReader archive(file_stream);
return file_matches_archive_member(expected, archive, part, content_type);
}
static bool string_matches_archive_member(const std::string &expected,
xlnt::zip_file &archive,
Partio::ZipFileReader &archive,
const xlnt::path &member,
const std::string &content_type)
{
return compare_files(expected, archive.read(member), content_type);
auto stream = archive.Get_File(member.string(), true);
std::string contents((std::istreambuf_iterator<char>(*stream)), (std::istreambuf_iterator<char>()));
delete stream;
return compare_files(expected, contents, content_type);
}
static bool file_matches_archive_member(const xlnt::path &file,
xlnt::zip_file &archive,
Partio::ZipFileReader &archive,
const xlnt::path &member,
const std::string &content_type)
{
if (!archive.has_file(member)) return false;
return compare_files(file.read_contents(), archive.read(member), content_type);
if (!archive.Has_File(member.string())) return false;
std::ostringstream member_stream;
auto stream = archive.Get_File(member.string(), true);
std::string contents((std::istreambuf_iterator<char>(*stream)), (std::istreambuf_iterator<char>()));
delete stream;
auto result = compare_files(file.read_contents(), contents, content_type);
}
static bool file_matches_document(const xlnt::path &expected,
@ -323,10 +331,19 @@ public:
return compare_files(string, ss.str(), content_type);
}
static bool xlsx_archives_match(xlnt::zip_file &left, xlnt::zip_file &right)
static bool xlsx_archives_match(const std::vector<std::uint8_t> &left, const std::vector<std::uint8_t> &right)
{
const auto left_info = left.infolist();
const auto right_info = right.infolist();
xlnt::detail::vector_istreambuf left_buffer(left);
std::istream left_stream(&left_buffer);
Partio::ZipFileReader left_archive(left_stream);
const auto left_info = left_archive.filename_to_header;
xlnt::detail::vector_istreambuf right_buffer(right);
std::istream right_stream(&right_buffer);
Partio::ZipFileReader right_archive(right_stream);
const auto right_info = right_archive.filename_to_header;
if (left_info.size() != right_info.size())
{
@ -335,52 +352,58 @@ public:
std::cout << "left has: ";
for (auto &info : left_info)
{
std::cout << info.filename.string() << ", ";
std::cout << info.first << ", ";
}
std::cout << std::endl;
std::cout << "right has: ";
for (auto &info : right_info)
{
std::cout << info.filename.string() << ", ";
std::cout << info.first << ", ";
}
std::cout << std::endl;
}
bool match = true;
std::vector<std::uint8_t> buffer;
left.save(buffer);
xlnt::workbook left_workbook;
left_workbook.load(buffer);
buffer.clear();
right.save(buffer);
xlnt::workbook right_workbook;
right_workbook.load(buffer);
right_workbook.load(right);
xlnt::workbook left_workbook;
left_workbook.load(left);
auto &left_manifest = left_workbook.get_manifest();
auto &right_manifest = right_workbook.get_manifest();
for (auto left_member : left_info)
{
if (!right.has_file(left_member))
if (!right_archive.Has_File(left_member.first))
{
match = false;
std::cout << "right is missing file: " << left_member.filename.string() << std::endl;
std::cout << "right is missing file: " << left_member.first << std::endl;
continue;
}
auto left_member_contents = left.read(left_member);
auto right_member_contents = right.read(left_member.filename);
std::unique_ptr<std::istream> left_member_stream(left_archive.Get_File(left_member.first));
std::vector<std::uint8_t> left_contents_raw;
xlnt::detail::vector_ostreambuf left_contents_buffer(left_contents_raw);
std::ostream left_contents_stream(&left_contents_buffer);
left_contents_stream << left_member_stream->rdbuf();
std::string left_member_contents(left_contents_raw.begin(), left_contents_raw.end());
std::unique_ptr<std::istream> right_member_stream(left_archive.Get_File(left_member.first));
std::vector<std::uint8_t> right_contents_raw;
xlnt::detail::vector_ostreambuf right_contents_buffer(right_contents_raw);
std::ostream right_contents_stream(&right_contents_buffer);
right_contents_stream << right_member_stream->rdbuf();
std::string right_member_contents(right_contents_raw.begin(), right_contents_raw.end());
std::string left_content_type, right_content_type;
if (left_member.filename.string() != "[Content_Types].xml")
if (left_member.first != "[Content_Types].xml")
{
left_content_type = left_manifest.get_content_type(xlnt::path(left_member.filename.string()));
right_content_type = right_manifest.get_content_type(xlnt::path(left_member.filename.string()));
left_content_type = left_manifest.get_content_type(xlnt::path(left_member.first));
right_content_type = right_manifest.get_content_type(xlnt::path(left_member.first));
}
else
{
@ -390,7 +413,7 @@ public:
if (left_content_type != right_content_type)
{
std::cout << "content types differ: "
<< left_member.filename.string()
<< left_member.first
<< " "
<< left_content_type
<< " "
@ -400,7 +423,7 @@ public:
}
else if (!compare_files(left_member_contents, right_member_contents, left_content_type))
{
std::cout << left_member.filename.string() << std::endl;
std::cout << left_member.first << std::endl;
match = false;
}
}

View File

@ -2,16 +2,12 @@ include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/common.cmake)
project(${LIBRARY_NAME}.third-party VERSION ${LIBRARY_VERSION} LANGUAGES CXX C)
# Includes
include_directories(miniz
libstudxml
include_directories(libstudxml
utfcpp/source
pole
botan
${LIBRARY_SOURCE_DIR}/detail)
zlib)
set(MINIZ
${CMAKE_CURRENT_SOURCE_DIR}/miniz/miniz.c
${CMAKE_CURRENT_SOURCE_DIR}/miniz/miniz.h)
set(LIBSTUDXML
${CMAKE_CURRENT_SOURCE_DIR}/libstudxml/xml/parser.cxx
${CMAKE_CURRENT_SOURCE_DIR}/libstudxml/xml/qname.cxx
@ -26,6 +22,21 @@ set(LIBSTUDXML
${CMAKE_CURRENT_SOURCE_DIR}/libstudxml/xml/details/genx/genx.c)
set(POLE pole/pole.cpp)
set(BOTAN ${CMAKE_CURRENT_SOURCE_DIR}/botan/botan_all.cpp)
set(ZLIB ${CMAKE_CURRENT_SOURCE_DIR}/zlib/adler32.c
${CMAKE_CURRENT_SOURCE_DIR}/zlib/compress.c
${CMAKE_CURRENT_SOURCE_DIR}/zlib/crc32.c
${CMAKE_CURRENT_SOURCE_DIR}/zlib/deflate.c
${CMAKE_CURRENT_SOURCE_DIR}/zlib/gzclose.c
${CMAKE_CURRENT_SOURCE_DIR}/zlib/gzlib.c
${CMAKE_CURRENT_SOURCE_DIR}/zlib/gzread.c
${CMAKE_CURRENT_SOURCE_DIR}/zlib/gzwrite.c
${CMAKE_CURRENT_SOURCE_DIR}/zlib/infback.c
${CMAKE_CURRENT_SOURCE_DIR}/zlib/inffast.c
${CMAKE_CURRENT_SOURCE_DIR}/zlib/inflate.c
${CMAKE_CURRENT_SOURCE_DIR}/zlib/inftrees.c
${CMAKE_CURRENT_SOURCE_DIR}/zlib/trees.c
${CMAKE_CURRENT_SOURCE_DIR}/zlib/uncompr.c
${CMAKE_CURRENT_SOURCE_DIR}/zlib/zutil.c)
if(MSVC)
set_source_files_properties(${BOTAN} PROPERTIES COMPILE_FLAGS "/wd\"4244\"")
@ -42,7 +53,7 @@ add_custom_command(OUTPUT ${BOTAN}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/botan
COMMENT "Generating botan amalgamation ${BOTAN}")
add_library(xlnt.third-party OBJECT ${MINIZ} ${LIBSTUDXML} ${POLE} ${BOTAN})
add_library(xlnt.third-party OBJECT ${LIBSTUDXML} ${POLE} ${BOTAN} ${ZLIB})
target_compile_definitions(xlnt.third-party PRIVATE LIBSTUDXML_STATIC_LIB=1)
if(NOT STATIC)
@ -55,5 +66,7 @@ if(MSVC)
set_target_properties(xlnt.third-party PROPERTIES COMPILE_FLAGS "/MP")
endif()
source_group(miniz FILES ${MINIZ})
source_group(botan FILES ${BOTAN})
source_group(libstudxml FILES ${LIBSTUDXML})
source_group(pole FILES ${LIBSTUDXML})
source_group(zlib FILES ${ZLIB})

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +0,0 @@
#define MINIZ_HEADER_FILE_ONLY
#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1
#define MINIZ_LITTLE_ENDIAN 1
#define MINIZ_HAS_64BIT_REGISTERS 1
#include "miniz.c"

1
third-party/zlib vendored Submodule

@ -0,0 +1 @@
Subproject commit 50893291621658f355bc5b4d450a8d06a563053d