separate crypto_helper into header file, fix lots of stuff

This commit is contained in:
Thomas Fussell 2017-02-17 23:11:06 -06:00
parent 84011553a8
commit 767d498dac
16 changed files with 905 additions and 732 deletions

View File

@ -183,9 +183,20 @@ public:
std::string hyperlink() const; std::string hyperlink() const;
/// <summary> /// <summary>
/// Adds a hyperlink to this cell pointing to the URI of the given value. /// Adds a hyperlink to this cell pointing to the URL of the given value.
/// </summary> /// </summary>
void hyperlink(const std::string &value); void hyperlink(const std::string &url);
/// <summary>
/// Adds a hyperlink to this cell pointing to the URI of the given value and sets
/// the text value of the cell to the given parameter.
/// </summary>
void hyperlink(const std::string &url, const std::string &display);
/// <summary>
/// Adds an internal hyperlink to this cell pointing to the given cell.
/// </summary>
void hyperlink(xlnt::cell target);
/// <summary> /// <summary>
/// Returns true if this cell has a hyperlink set. /// Returns true if this cell has a hyperlink set.

View File

@ -450,20 +450,20 @@ public:
/// Serializes the workbook into an XLSX file encrypted with the given password /// Serializes the workbook into an XLSX file encrypted with the given password
/// and loads the bytes into a file named filename. /// and loads the bytes into a file named filename.
/// </summary> /// </summary>
void save(const std::string &filename, const std::string &password); void save(const std::string &filename, const std::string &password) const;
#ifdef _MSC_VER #ifdef _MSC_VER
/// <summary> /// <summary>
/// Serializes the workbook into an XLSX file and saves the data into a file /// Serializes the workbook into an XLSX file and saves the data into a file
/// named filename. /// named filename.
/// </summary> /// </summary>
void save(const std::wstring &filename); void save(const std::wstring &filename) const;
/// <summary> /// <summary>
/// Serializes the workbook into an XLSX file encrypted with the given password /// Serializes the workbook into an XLSX file encrypted with the given password
/// and loads the bytes into a file named filename. /// and loads the bytes into a file named filename.
/// </summary> /// </summary>
void save(const std::wstring &filename, const std::string &password); void save(const std::wstring &filename, const std::string &password) const;
#endif #endif
/// <summary> /// <summary>
@ -476,7 +476,7 @@ public:
/// Serializes the workbook into an XLSX file encrypted with the given password /// Serializes the workbook into an XLSX file encrypted with the given password
/// and loads the bytes into a file named filename. /// and loads the bytes into a file named filename.
/// </summary> /// </summary>
void save(const xlnt::path &filename, const std::string &password); void save(const xlnt::path &filename, const std::string &password) const;
/// <summary> /// <summary>
/// Serializes the workbook into an XLSX file and saves the data into stream. /// Serializes the workbook into an XLSX file and saves the data into stream.
@ -487,7 +487,7 @@ public:
/// Serializes the workbook into an XLSX file encrypted with the given password /// Serializes the workbook into an XLSX file encrypted with the given password
/// and loads the bytes into the given stream. /// and loads the bytes into the given stream.
/// </summary> /// </summary>
void save(std::ostream &stream, const std::string &password); void save(std::ostream &stream, const std::string &password) const;
/// <summary> /// <summary>
/// Interprets byte vector data as an XLSX file and sets the content of this /// Interprets byte vector data as an XLSX file and sets the content of this

View File

@ -742,6 +742,11 @@ private:
/// </summary> /// </summary>
void register_comments_in_manifest(); void register_comments_in_manifest();
/// <summary>
/// Add the calcChain part to the workbook if it doesn't already exist.
/// </summary>
void register_calc_chain_in_manifest();
/// <summary> /// <summary>
/// Removes calcChain part from manifest if no formulae remain in workbook. /// Removes calcChain part from manifest if no formulae remain in workbook.
/// </summary> /// </summary>

View File

@ -446,18 +446,24 @@ void cell::hyperlink(const std::string &hyperlink)
} }
d_->hyperlink_ = hyperlink; d_->hyperlink_ = hyperlink;
if (data_type() == type::null)
{
value(hyperlink);
} }
void cell::hyperlink(const std::string &url, const std::string &display)
{
hyperlink(url);
value(display);
}
void cell::hyperlink(xlnt::cell /*target*/)
{
//todo: implement
} }
void cell::formula(const std::string &formula) void cell::formula(const std::string &formula)
{ {
if (formula.empty()) if (formula.empty())
{ {
throw invalid_parameter(); return clear_formula();
} }
if (formula[0] == '=') if (formula[0] == '=')
@ -468,6 +474,8 @@ void cell::formula(const std::string &formula)
{ {
d_->formula_ = formula; d_->formula_ = formula;
} }
worksheet().register_calc_chain_in_manifest();
} }
bool cell::has_formula() const bool cell::has_formula() const

View File

@ -140,7 +140,7 @@ public:
auto cell = ws.cell(xlnt::cell_reference(1, 1)); auto cell = ws.cell(xlnt::cell_reference(1, 1));
TS_ASSERT(!cell.has_formula()); TS_ASSERT(!cell.has_formula());
TS_ASSERT_THROWS(cell.formula(""), xlnt::invalid_parameter); TS_ASSERT_THROWS_NOTHING(cell.formula(""));
TS_ASSERT(!cell.has_formula()); TS_ASSERT(!cell.has_formula());
cell.formula("=42"); cell.formula("=42");
TS_ASSERT(cell.has_formula()); TS_ASSERT(cell.has_formula());

View File

@ -95,9 +95,9 @@ struct workbook_impl
optional<theme> theme_; optional<theme> theme_;
std::unordered_map<std::string, std::vector<std::uint8_t>> images_; std::unordered_map<std::string, std::vector<std::uint8_t>> images_;
std::unordered_map<xlnt::core_property, variant, xlnt::scoped_enum_hash<xlnt::core_property>> core_properties_; std::vector<std::pair<xlnt::core_property, variant>> core_properties_;
std::unordered_map<xlnt::extended_property, variant, xlnt::scoped_enum_hash<xlnt::extended_property>> extended_properties_; std::vector<std::pair<xlnt::extended_property, variant>> extended_properties_;
std::unordered_map<std::string, variant> custom_properties_; std::vector<std::pair<std::string, variant>> custom_properties_;
std::unordered_map<std::string, std::string> sheet_title_rel_id_map_; std::unordered_map<std::string, std::string> sheet_title_rel_id_map_;

View File

@ -21,37 +21,20 @@
// @license: http://www.opensource.org/licenses/mit-license.php // @license: http://www.opensource.org/licenses/mit-license.php
// @author: see AUTHORS file // @author: see AUTHORS file
#include <array> #include <detail/xlsx_crypto.hpp>
#include <xlnt/utils/exceptions.hpp> namespace {
#include <xlnt/workbook/workbook.hpp>
#include <detail/constants.hpp>
#include <detail/include_cryptopp.hpp>
#include <detail/include_libstudxml.hpp>
#include <detail/pole.hpp>
#include <detail/vector_streambuf.hpp>
#include <detail/xlsx_consumer.hpp>
#include <detail/xlsx_producer.hpp>
namespace xlnt { template <typename T>
namespace detail { auto read_int(std::size_t &index, const std::vector<std::uint8_t> &raw_data)
enum class hash_algorithm
{ {
sha1, auto result = *reinterpret_cast<const T *>(&raw_data[index]);
sha256, index += sizeof(T);
sha384,
sha512,
md5,
md4,
md2,
ripemd128,
ripemd160,
whirlpool
};
} // namespace detail return result;
} // namespace xlnt }
} // namespace
namespace xml { namespace xml {
@ -116,36 +99,11 @@ struct value_traits<xlnt::detail::hash_algorithm>
namespace xlnt { namespace xlnt {
namespace detail { namespace detail {
struct crypto_helper std::vector<std::uint8_t> crypto_helper::aes(
{ const std::vector<std::uint8_t> &key,
static const std::size_t segment_length; const std::vector<std::uint8_t> &iv,
const std::vector<std::uint8_t> &source,
enum class cipher_algorithm cipher_chaining chaining, cipher_direction direction)
{
aes,
rc2,
rc4,
des,
desx,
triple_des,
triple_des_112
};
enum class cipher_chaining
{
ecb, // electronic code book
cbc, // cipher block chaining
cfb // cipher feedback chaining
};
enum class cipher_direction
{
encryption,
decryption
};
static std::vector<std::uint8_t> aes(const std::vector<std::uint8_t> &key, const std::vector<std::uint8_t> &iv,
const std::vector<std::uint8_t> &source, cipher_chaining chaining, cipher_direction direction)
{ {
std::vector<std::uint8_t> destination(source.size(), 0); std::vector<std::uint8_t> destination(source.size(), 0);
@ -193,7 +151,7 @@ struct crypto_helper
return destination; return destination;
} }
static std::vector<std::uint8_t> decode_base64(const std::string &encoded) std::vector<std::uint8_t> crypto_helper::decode_base64(const std::string &encoded)
{ {
CryptoPP::Base64Decoder decoder; CryptoPP::Base64Decoder decoder;
decoder.Put(reinterpret_cast<const std::uint8_t *>(encoded.data()), encoded.size()); decoder.Put(reinterpret_cast<const std::uint8_t *>(encoded.data()), encoded.size());
@ -205,7 +163,7 @@ struct crypto_helper
return decoded; return decoded;
} }
static std::string encode_base64(const std::vector<std::uint8_t> &decoded) std::string crypto_helper::encode_base64(const std::vector<std::uint8_t> &decoded)
{ {
CryptoPP::Base64Decoder encoder; CryptoPP::Base64Decoder encoder;
encoder.Put(reinterpret_cast<const std::uint8_t *>(decoded.data()), decoded.size()); encoder.Put(reinterpret_cast<const std::uint8_t *>(decoded.data()), decoded.size());
@ -217,7 +175,7 @@ struct crypto_helper
return std::string(encoded.begin(), encoded.end()); return std::string(encoded.begin(), encoded.end());
} }
static std::vector<std::uint8_t> hash(hash_algorithm algorithm, const std::vector<std::uint8_t> &input) std::vector<std::uint8_t> crypto_helper::hash(hash_algorithm algorithm, const std::vector<std::uint8_t> &input)
{ {
std::vector<std::uint8_t> digest; std::vector<std::uint8_t> digest;
@ -237,7 +195,7 @@ struct crypto_helper
return digest; return digest;
} }
static std::vector<std::uint8_t> file(POLE::Storage &storage, const std::string &name) std::vector<std::uint8_t> crypto_helper::file(POLE::Storage &storage, const std::string &name)
{ {
POLE::Stream stream(&storage, name.c_str()); POLE::Stream stream(&storage, name.c_str());
if (stream.fail()) return {}; if (stream.fail()) return {};
@ -246,33 +204,10 @@ struct crypto_helper
return bytes; return bytes;
} }
template <typename T> std::vector<std::uint8_t> crypto_helper::decrypt_xlsx_standard(
static auto read_int(std::size_t &index, const std::vector<std::uint8_t> &raw_data) const std::vector<std::uint8_t> &encryption_info,
{ const std::string &password,
auto result = *reinterpret_cast<const T *>(&raw_data[index]); const std::vector<std::uint8_t> &encrypted_package)
index += sizeof(T);
return result;
}
struct standard_encryption_info
{
const std::size_t spin_count = 50000;
std::size_t block_size;
std::size_t key_bits;
std::size_t key_bytes;
std::size_t hash_size;
cipher_algorithm cipher;
cipher_chaining chaining;
const hash_algorithm hash = hash_algorithm::sha1;
std::vector<std::uint8_t> salt_value;
std::vector<std::uint8_t> verifier_hash_input;
std::vector<std::uint8_t> verifier_hash_value;
std::vector<std::uint8_t> encrypted_key_value;
};
static std::vector<std::uint8_t> decrypt_xlsx_standard(const std::vector<std::uint8_t> &encryption_info,
const std::string &password, const std::vector<std::uint8_t> &encrypted_package)
{ {
std::size_t offset = 0; std::size_t offset = 0;
@ -397,60 +332,22 @@ struct crypto_helper
std::size_t package_offset = 0; std::size_t package_offset = 0;
auto decrypted_size = static_cast<std::size_t>(read_int<std::uint64_t>(package_offset, encrypted_package)); auto decrypted_size = static_cast<std::size_t>(read_int<std::uint64_t>(package_offset, encrypted_package));
auto decrypted = auto decrypted = aes(key_derived, {},
aes(key_derived, {}, std::vector<std::uint8_t>(encrypted_package.begin() + 8, encrypted_package.end()), std::vector<std::uint8_t>(encrypted_package.begin() + 8, encrypted_package.end()),
cipher_chaining::ecb, cipher_direction::decryption); cipher_chaining::ecb, cipher_direction::decryption);
decrypted.resize(decrypted_size); decrypted.resize(decrypted_size);
return decrypted; return decrypted;
} }
struct agile_encryption_info crypto_helper::agile_encryption_info crypto_helper::generate_agile_encryption_info(const std::string &password)
{
// key data
struct
{
std::size_t salt_size;
std::size_t block_size;
std::size_t key_bits;
std::size_t hash_size;
std::string cipher_algorithm;
std::string cipher_chaining;
std::string hash_algorithm;
std::vector<std::uint8_t> salt_value;
} key_data;
struct
{
std::vector<std::uint8_t> hmac_key;
std::vector<std::uint8_t> hmac_value;
} data_integrity;
struct
{
std::size_t spin_count;
std::size_t salt_size;
std::size_t block_size;
std::size_t key_bits;
std::size_t hash_size;
std::string cipher_algorithm;
std::string cipher_chaining;
hash_algorithm hash;
std::vector<std::uint8_t> salt_value;
std::vector<std::uint8_t> verifier_hash_input;
std::vector<std::uint8_t> verifier_hash_value;
std::vector<std::uint8_t> encrypted_key_value;
} key_encryptor;
};
static agile_encryption_info generate_agile_encryption_info(const std::string &password)
{ {
agile_encryption_info result; agile_encryption_info result;
result.key_data.salt_value.assign(password.begin(), password.end()); result.key_data.salt_value.assign(password.begin(), password.end());
return result; return result;
} }
static std::vector<std::uint8_t> write_agile_encryption_info(const std::string &password) std::vector<std::uint8_t> crypto_helper::write_agile_encryption_info(const std::string &password)
{ {
static const auto &xmlns = xlnt::constants::ns("encryption"); static const auto &xmlns = xlnt::constants::ns("encryption");
static const auto &xmlns_p = xlnt::constants::ns("encryption-password"); static const auto &xmlns_p = xlnt::constants::ns("encryption-password");
@ -505,8 +402,10 @@ struct crypto_helper
return encryption_info; return encryption_info;
} }
static std::vector<std::uint8_t> decrypt_xlsx_agile(const std::vector<std::uint8_t> &encryption_info, std::vector<std::uint8_t> crypto_helper::decrypt_xlsx_agile(
const std::string &password, const std::vector<std::uint8_t> &encrypted_package) const std::vector<std::uint8_t> &encryption_info,
const std::string &password,
const std::vector<std::uint8_t> &encrypted_package)
{ {
static const auto &xmlns = xlnt::constants::ns("encryption"); static const auto &xmlns = xlnt::constants::ns("encryption");
static const auto &xmlns_p = xlnt::constants::ns("encryption-password"); static const auto &xmlns_p = xlnt::constants::ns("encryption-password");
@ -630,8 +529,8 @@ struct crypto_helper
combined.insert(combined.end(), block.begin(), block.end()); combined.insert(combined.end(), block.begin(), block.end());
auto key = hash(result.key_encryptor.hash, combined); auto key = hash(result.key_encryptor.hash, combined);
key.resize(result.key_encryptor.key_bits / 8); key.resize(result.key_encryptor.key_bits / 8);
return aes( return aes(key, result.key_encryptor.salt_value, encrypted,
key, result.key_encryptor.salt_value, encrypted, cipher_chaining::cbc, cipher_direction::decryption); cipher_chaining::cbc, cipher_direction::decryption);
}; };
const std::array<std::uint8_t, block_size> input_block_key = {{0xfe, 0xa7, 0xd2, 0x76, 0x3b, 0x4b, 0x9e, 0x79}}; const std::array<std::uint8_t, block_size> input_block_key = {{0xfe, 0xa7, 0xd2, 0x76, 0x3b, 0x4b, 0x9e, 0x79}};
@ -689,7 +588,8 @@ struct crypto_helper
return decrypted_package; return decrypted_package;
} }
static std::vector<std::uint8_t> decrypt_xlsx(const std::vector<std::uint8_t> &bytes, const std::string &password) std::vector<std::uint8_t> crypto_helper::decrypt_xlsx(
const std::vector<std::uint8_t> &bytes, const std::string &password)
{ {
if (bytes.empty()) if (bytes.empty())
{ {
@ -752,7 +652,8 @@ struct crypto_helper
return decrypt_xlsx_standard(encryption_info, password, encrypted_package); return decrypt_xlsx_standard(encryption_info, password, encrypted_package);
} }
static std::vector<std::uint8_t> encrypt_xlsx(const std::vector<std::uint8_t> &bytes, const std::string &password) std::vector<std::uint8_t> crypto_helper::encrypt_xlsx(
const std::vector<std::uint8_t> &bytes, const std::string &password)
{ {
if (bytes.empty()) if (bytes.empty())
{ {
@ -763,7 +664,6 @@ struct crypto_helper
return {}; return {};
} }
};
const std::size_t crypto_helper::segment_length = 4096; const std::size_t crypto_helper::segment_length = 4096;

View File

@ -0,0 +1,162 @@
// Copyright (c) 2014-2017 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
#include <array>
#include <xlnt/utils/exceptions.hpp>
#include <xlnt/workbook/workbook.hpp>
#include <detail/constants.hpp>
#include <detail/include_cryptopp.hpp>
#include <detail/include_libstudxml.hpp>
#include <detail/pole.hpp>
#include <detail/vector_streambuf.hpp>
#include <detail/xlsx_consumer.hpp>
#include <detail/xlsx_producer.hpp>
namespace xlnt {
namespace detail {
enum class hash_algorithm
{
sha1,
sha256,
sha384,
sha512,
md5,
md4,
md2,
ripemd128,
ripemd160,
whirlpool
};
struct crypto_helper
{
static const std::size_t segment_length;
enum class cipher_algorithm
{
aes,
rc2,
rc4,
des,
desx,
triple_des,
triple_des_112
};
enum class cipher_chaining
{
ecb, // electronic code book
cbc, // cipher block chaining
cfb // cipher feedback chaining
};
enum class cipher_direction
{
encryption,
decryption
};
static std::vector<std::uint8_t> aes(const std::vector<std::uint8_t> &key, const std::vector<std::uint8_t> &iv,
const std::vector<std::uint8_t> &source, cipher_chaining chaining, cipher_direction direction);
static std::vector<std::uint8_t> decode_base64(const std::string &encoded);
static std::string encode_base64(const std::vector<std::uint8_t> &decoded);
static std::vector<std::uint8_t> hash(hash_algorithm algorithm, const std::vector<std::uint8_t> &input);
static std::vector<std::uint8_t> file(POLE::Storage &storage, const std::string &name);
struct standard_encryption_info
{
const std::size_t spin_count = 50000;
std::size_t block_size;
std::size_t key_bits;
std::size_t key_bytes;
std::size_t hash_size;
cipher_algorithm cipher;
cipher_chaining chaining;
const hash_algorithm hash = hash_algorithm::sha1;
std::vector<std::uint8_t> salt_value;
std::vector<std::uint8_t> verifier_hash_input;
std::vector<std::uint8_t> verifier_hash_value;
std::vector<std::uint8_t> encrypted_key_value;
};
static std::vector<std::uint8_t> decrypt_xlsx_standard(const std::vector<std::uint8_t> &encryption_info,
const std::string &password, const std::vector<std::uint8_t> &encrypted_package);
struct agile_encryption_info
{
// key data
struct
{
std::size_t salt_size;
std::size_t block_size;
std::size_t key_bits;
std::size_t hash_size;
std::string cipher_algorithm;
std::string cipher_chaining;
std::string hash_algorithm;
std::vector<std::uint8_t> salt_value;
} key_data;
struct
{
std::vector<std::uint8_t> hmac_key;
std::vector<std::uint8_t> hmac_value;
} data_integrity;
struct
{
std::size_t spin_count;
std::size_t salt_size;
std::size_t block_size;
std::size_t key_bits;
std::size_t hash_size;
std::string cipher_algorithm;
std::string cipher_chaining;
hash_algorithm hash;
std::vector<std::uint8_t> salt_value;
std::vector<std::uint8_t> verifier_hash_input;
std::vector<std::uint8_t> verifier_hash_value;
std::vector<std::uint8_t> encrypted_key_value;
} key_encryptor;
};
static agile_encryption_info generate_agile_encryption_info(const std::string &password);
static std::vector<std::uint8_t> write_agile_encryption_info(const std::string &password);
static std::vector<std::uint8_t> decrypt_xlsx_agile(const std::vector<std::uint8_t> &encryption_info,
const std::string &password, const std::vector<std::uint8_t> &encrypted_package);
static std::vector<std::uint8_t> decrypt_xlsx(const std::vector<std::uint8_t> &bytes, const std::string &password);
static std::vector<std::uint8_t> encrypt_xlsx(const std::vector<std::uint8_t> &bytes, const std::string &password);
};
} // namespace detail
} // namespace xlnt

View File

@ -218,7 +218,7 @@ void xlsx_producer::write_property(const std::string &name, const variant &value
write_start_element(constants::ns("vt"), "bool"); write_start_element(constants::ns("vt"), "bool");
} }
write_characters(write_bool(value.get<bool>())); write_characters(value.get<bool>() ? "true" : "false");
if (custom) if (custom)
{ {
@ -336,6 +336,7 @@ void xlsx_producer::write_core_properties(const relationship &/*rel*/)
for (const auto &ns : core_property_namespace(prop)) for (const auto &ns : core_property_namespace(prop))
{ {
if (namespaces.count(ns.first) > 0) continue; if (namespaces.count(ns.first) > 0) continue;
write_namespace(ns.first, ns.second); write_namespace(ns.first, ns.second);
namespaces.emplace(ns); namespaces.emplace(ns);
} }

View File

@ -432,7 +432,7 @@ protected:
virtual int underflow() virtual int underflow()
{ {
throw std::runtime_error("Attempt to read write only ostream"); throw xlnt::exception("Attempt to read write only ostream");
} }
virtual int overflow(int c = EOF); virtual int overflow(int c = EOF);
@ -494,7 +494,7 @@ izstream::izstream(std::istream &stream)
{ {
if (!stream) if (!stream)
{ {
throw std::runtime_error("ZIP: Invalid file handle"); throw xlnt::exception("Invalid file handle");
} }
read_central_header(); read_central_header();
@ -522,15 +522,20 @@ bool izstream::read_central_header()
} }
source_stream_.seekg(end_position - read_start); source_stream_.seekg(end_position - read_start);
std::vector<char> buf(static_cast<std::size_t>(read_start), '\0'); std::vector<std::uint8_t> buf(static_cast<std::size_t>(read_start), '\0');
if (read_start <= 0) if (read_start <= 0)
{ {
std::cerr << "ZIP: Invalid read buffer size" << std::endl; throw xlnt::exception("file is empty");
return false;
} }
source_stream_.read(buf.data(), read_start); source_stream_.read(reinterpret_cast<char *>(buf.data()), read_start);
if (buf[0] == 0xd0 && buf[1] == 0xcf && buf[2] == 0x11 && buf[3] == 0xe0
&& buf[4] == 0xa1 && buf[5] == 0xb1 && buf[6] == 0x1a && buf[7] == 0xe1)
{
throw xlnt::exception("encrypted xlsx, password required");
}
auto found_header = false; auto found_header = false;
std::size_t header_index = 0; std::size_t header_index = 0;
@ -547,8 +552,7 @@ bool izstream::read_central_header()
if (!found_header) if (!found_header)
{ {
std::cerr << "ZIP: Failed to find zip header" << std::endl; throw xlnt::exception("failed to find zip header");
return false;
} }
// seek to end of central header and read // seek to end of central header and read
@ -560,8 +564,7 @@ bool izstream::read_central_header()
if (disk_number1 != disk_number2 || disk_number1 != 0) if (disk_number1 != disk_number2 || disk_number1 != 0)
{ {
std::cerr << "ZIP: multiple disk zip files are not supported" << std::endl; throw xlnt::exception("multiple disk zip files are not supported");
return false;
} }
auto num_files = read_int<std::uint16_t>(source_stream_); // one entry in center in this disk auto num_files = read_int<std::uint16_t>(source_stream_); // one entry in center in this disk
@ -569,8 +572,7 @@ bool izstream::read_central_header()
if (num_files != num_files_this_disk) if (num_files != num_files_this_disk)
{ {
std::cerr << "ZIP: multi disk zip files are not supported" << std::endl; throw xlnt::exception("multi disk zip files are not supported");
return false;
} }
/*auto size_of_header = */ read_int<std::uint32_t>(source_stream_); // size of header /*auto size_of_header = */ read_int<std::uint32_t>(source_stream_); // size of header
@ -592,7 +594,7 @@ std::unique_ptr<std::streambuf> izstream::open(const path &filename) const
{ {
if (!has_file(filename)) if (!has_file(filename))
{ {
throw "not found"; throw xlnt::exception("file not found");
} }
auto header = file_headers_.at(filename.string()); auto header = file_headers_.at(filename.string());

View File

@ -134,7 +134,7 @@ public:
TS_ASSERT(!temp_buffer.empty()); TS_ASSERT(!temp_buffer.empty());
} }
void test_write_comments() void test_write_comments_hyperlinks_formulae()
{ {
xlnt::workbook wb; xlnt::workbook wb;
auto sheet1 = wb.active_sheet(); auto sheet1 = wb.active_sheet();
@ -146,6 +146,15 @@ public:
sheet1.cell("A2").value("Sheet1!A2"); sheet1.cell("A2").value("Sheet1!A2");
sheet1.cell("A2").comment("Sheet1 comment2", comment_font, "Microsoft Office User"); sheet1.cell("A2").comment("Sheet1 comment2", comment_font, "Microsoft Office User");
sheet1.cell("A4").hyperlink("https://microsoft.com", "hyperlink1");
sheet1.cell("A5").hyperlink("https://google.com");
sheet1.cell("A6").hyperlink(sheet1.cell("A1"));
sheet1.cell("A7").hyperlink("mailto:invalid@example.com?subject=important");
sheet1.cell("C1").formula("=CONCATENATE(C2,C3)");
sheet1.cell("C2").value("a");
sheet1.cell("C3").value("b");
auto sheet2 = wb.create_sheet(); auto sheet2 = wb.create_sheet();
sheet2.cell("A1").value("Sheet2!A1"); sheet2.cell("A1").value("Sheet2!A1");
sheet2.cell("A2").comment("Sheet2 comment", comment_font, "Microsoft Office User"); sheet2.cell("A2").comment("Sheet2 comment", comment_font, "Microsoft Office User");
@ -153,6 +162,12 @@ public:
sheet2.cell("A2").value("Sheet2!A2"); sheet2.cell("A2").value("Sheet2!A2");
sheet2.cell("A2").comment("Sheet2 comment2", comment_font, "Microsoft Office User"); sheet2.cell("A2").comment("Sheet2 comment2", comment_font, "Microsoft Office User");
sheet2.cell("A4").hyperlink("https://apple.com", "hyperlink2");
sheet2.cell("C1").formula("=C2*C3");
sheet2.cell("C2").value(2);
sheet2.cell("C3").value(3);
const auto path = path_helper::data_directory("10_comments_hyperlinks_formulae.xlsx"); const auto path = path_helper::data_directory("10_comments_hyperlinks_formulae.xlsx");
TS_ASSERT(workbook_matches_file(wb, path)); TS_ASSERT(workbook_matches_file(wb, path));
} }

View File

@ -5,6 +5,7 @@
#include <cxxtest/TestSuite.h> #include <cxxtest/TestSuite.h>
#include <detail/vector_streambuf.hpp> #include <detail/vector_streambuf.hpp>
#include <detail/xlsx_crypto.hpp>
#include <helpers/path_helper.hpp> #include <helpers/path_helper.hpp>
#include <helpers/xml_helper.hpp> #include <helpers/xml_helper.hpp>
#include <xlnt/workbook/workbook.hpp> #include <xlnt/workbook/workbook.hpp>
@ -16,25 +17,32 @@ public:
/// Read file as an XLSX-formatted ZIP file in the filesystem to a workbook, /// Read file as an XLSX-formatted ZIP file in the filesystem to a workbook,
/// write the workbook back to memory, then ensure that the contents of the two files are equivalent. /// write the workbook back to memory, then ensure that the contents of the two files are equivalent.
/// </summary> /// </summary>
bool round_trip_matches_rw(const xlnt::path &original) bool round_trip_matches_rw(const xlnt::path &source)
{ {
std::ifstream file_stream(original.string(), std::ios::binary); xlnt::workbook source_workbook;
std::vector<std::uint8_t> original_data; source_workbook.load(source);
{ std::vector<std::uint8_t> destination;
xlnt::detail::vector_ostreambuf file_data_buffer(original_data); source_workbook.save(destination);
std::ostream file_data_stream(&file_data_buffer);
file_data_stream << file_stream.rdbuf(); std::ifstream source_stream(source.string(), std::ios::binary);
return xml_helper::xlsx_archives_match(xlnt::detail::to_vector(source_stream), destination);
} }
xlnt::workbook original_workbook; bool round_trip_matches_rw(const xlnt::path &source, const std::string &password)
original_workbook.load(original); {
xlnt::workbook source_workbook;
source_workbook.load(source, password);
std::vector<std::uint8_t> buffer; std::vector<std::uint8_t> destination;
original_workbook.save(buffer); source_workbook.save(destination);
original_workbook.save("round_trip_out.xlsx");
return xml_helper::xlsx_archives_match(original_data, buffer); std::ifstream source_stream(source.string(), std::ios::binary);
const auto source_decrypted = xlnt::detail::crypto_helper::decrypt_xlsx(
xlnt::detail::to_vector(source_stream), password);
return xml_helper::xlsx_archives_match(source_decrypted, destination);
} }
void test_round_trip_empty_excel_rw() void test_round_trip_empty_excel_rw()
@ -44,10 +52,6 @@ public:
"2_minimal", "2_minimal",
"3_default", "3_default",
"4_every_style", "4_every_style",
"5_encrypted_agile",
"6_encrypted_libre",
"7_encrypted_standard",
"8_encrypted_numbers",
"10_comments_hyperlinks_formulae", "10_comments_hyperlinks_formulae",
"11_print_settings", "11_print_settings",
"12_advanced_properties" "12_advanced_properties"
@ -59,4 +63,22 @@ public:
TS_ASSERT(round_trip_matches_rw(path)); TS_ASSERT(round_trip_matches_rw(path));
} }
} }
void test_round_trip_empty_excel_rw_encrypted()
{
const auto files = std::vector<std::string>
{
"5_encrypted_agile",
"6_encrypted_libre",
"7_encrypted_standard",
"8_encrypted_numbers"
};
for (const auto file : files)
{
auto path = path_helper::data_directory(file + ".xlsx");
TS_ASSERT(round_trip_matches_rw(path, file
== "7_encrypted_standard" ? "password" : "secret"));
}
}
}; };

View File

@ -107,9 +107,9 @@ void open_stream(std::ofstream &stream, const std::string &path)
#endif #endif
template<typename T> template<typename T>
std::vector<typename T::key_type> keys(const T &container) std::vector<T> keys(const std::vector<std::pair<T, xlnt::variant>> &container)
{ {
auto result = std::vector<typename T::key_type>(); auto result = std::vector<T>();
auto iter = container.begin(); auto iter = container.begin();
while (iter != container.end()) while (iter != container.end())
@ -120,6 +120,20 @@ std::vector<typename T::key_type> keys(const T &container)
return result; return result;
} }
template<typename T>
bool contains(const std::vector<std::pair<T, xlnt::variant>> &container, const T key)
{
for (const auto &iter : container)
{
if (iter.first == key)
{
return true;
}
}
return false;
}
xlnt::path default_path(xlnt::relationship_type type, std::size_t index = 0) xlnt::path default_path(xlnt::relationship_type type, std::size_t index = 0)
{ {
using xlnt::path; using xlnt::path;
@ -285,7 +299,7 @@ namespace xlnt {
bool workbook::has_core_property(xlnt::core_property type) const bool workbook::has_core_property(xlnt::core_property type) const
{ {
return d_->core_properties_.count(type) > 0; return ::contains(d_->core_properties_, type);
} }
std::vector<xlnt::core_property> workbook::core_properties() const std::vector<xlnt::core_property> workbook::core_properties() const
@ -295,18 +309,36 @@ std::vector<xlnt::core_property> workbook::core_properties() const
variant workbook::core_property(xlnt::core_property type) const variant workbook::core_property(xlnt::core_property type) const
{ {
return d_->core_properties_.at(type); for (auto iter : d_->core_properties_)
{
if (iter.first == type)
{
return iter.second;
}
}
throw xlnt::exception("workbook doesn't have core property");
} }
void workbook::core_property(xlnt::core_property type, const variant &value) void workbook::core_property(xlnt::core_property type, const variant &value)
{ {
register_package_part(relationship_type::core_properties); register_package_part(relationship_type::core_properties);
d_->core_properties_[type] = value;
for (auto &iter : d_->core_properties_)
{
if (iter.first == type)
{
iter.second = value;
return;
}
}
d_->core_properties_.push_back({type, value});
} }
bool workbook::has_extended_property(xlnt::extended_property type) const bool workbook::has_extended_property(xlnt::extended_property type) const
{ {
return d_->extended_properties_.count(type) > 0; return ::contains(d_->extended_properties_, type);
} }
std::vector<xlnt::extended_property> workbook::extended_properties() const std::vector<xlnt::extended_property> workbook::extended_properties() const
@ -317,17 +349,35 @@ std::vector<xlnt::extended_property> workbook::extended_properties() const
void workbook::extended_property(xlnt::extended_property type, const variant &value) void workbook::extended_property(xlnt::extended_property type, const variant &value)
{ {
register_package_part(relationship_type::extended_properties); register_package_part(relationship_type::extended_properties);
d_->extended_properties_[type] = value;
for (auto &iter : d_->extended_properties_)
{
if (iter.first == type)
{
iter.second = value;
return;
}
}
d_->extended_properties_.push_back({type, value});
} }
variant workbook::extended_property(xlnt::extended_property type) const variant workbook::extended_property(xlnt::extended_property type) const
{ {
return d_->extended_properties_.at(type); for (auto iter : d_->extended_properties_)
{
if (iter.first == type)
{
return iter.second;
}
}
throw xlnt::exception("workbook doesn't have extended property");
} }
bool workbook::has_custom_property(const std::string &property_name) const bool workbook::has_custom_property(const std::string &property_name) const
{ {
return d_->custom_properties_.count(property_name) > 0; return ::contains(d_->custom_properties_, property_name);
} }
std::vector<std::string> workbook::custom_properties() const std::vector<std::string> workbook::custom_properties() const
@ -338,12 +388,30 @@ std::vector<std::string> workbook::custom_properties() const
void workbook::custom_property(const std::string &property_name, const variant &value) void workbook::custom_property(const std::string &property_name, const variant &value)
{ {
register_package_part(relationship_type::custom_properties); register_package_part(relationship_type::custom_properties);
d_->custom_properties_[property_name] = value;
for (auto &iter : d_->custom_properties_)
{
if (iter.first == property_name)
{
iter.second = value;
return;
}
}
d_->custom_properties_.push_back({property_name, value});
} }
variant workbook::custom_property(const std::string &property_name) const variant workbook::custom_property(const std::string &property_name) const
{ {
return d_->custom_properties_.at(property_name); for (auto iter : d_->custom_properties_)
{
if (iter.first == property_name)
{
return iter.second;
}
}
throw xlnt::exception("workbook doesn't have custom property");
} }
workbook workbook::empty() workbook workbook::empty()
@ -367,7 +435,9 @@ workbook workbook::empty()
wb.extended_property(xlnt::extended_property::application, "Microsoft Macintosh Excel"); wb.extended_property(xlnt::extended_property::application, "Microsoft Macintosh Excel");
wb.extended_property(xlnt::extended_property::doc_security, 0); wb.extended_property(xlnt::extended_property::doc_security, 0);
wb.extended_property(xlnt::extended_property::scale_crop, "false"); wb.extended_property(xlnt::extended_property::scale_crop, false);
wb.extended_property(xlnt::extended_property::heading_pairs, std::vector<variant>{variant("Worksheets"), variant(1)});
wb.extended_property(xlnt::extended_property::titles_of_parts, {"Sheet1"});
wb.extended_property(xlnt::extended_property::company, ""); wb.extended_property(xlnt::extended_property::company, "");
wb.extended_property(xlnt::extended_property::links_up_to_date, false); wb.extended_property(xlnt::extended_property::links_up_to_date, false);
wb.extended_property(xlnt::extended_property::shared_doc, false); wb.extended_property(xlnt::extended_property::shared_doc, false);
@ -793,25 +863,6 @@ void workbook::load(const std::vector<std::uint8_t> &data)
void workbook::load(const std::string &filename) void workbook::load(const std::string &filename)
{ {
if (filename.find_last_of(".") != std::string::npos) // check extension
{
std::string file_format = path(filename).extension();
if (file_format == "xls") {
throw xlnt::exception(" xlnt does not support the old .xls file format");
}
else if (file_format == "xlsb") {
throw xlnt::exception(" xlnt does not support the .xlsb file format");
}
else if (file_format != "xlsx") {
throw xlnt::exception(" xlnt does not support the ."+file_format+ " file format");
}
}
else
{
throw xlnt::exception("file has no extension .xlsx");
}
return load(path(filename)); return load(path(filename));
} }
@ -830,25 +881,6 @@ void workbook::load(const path &filename)
void workbook::load(const std::string &filename, const std::string &password) void workbook::load(const std::string &filename, const std::string &password)
{ {
if (filename.find_last_of(".") != std::string::npos) // check extension
{
std::string file_format = path(filename).extension();
if (file_format == "xls") {
throw xlnt::exception(" xlnt does not support the old .xls file format");
}
else if (file_format == "xlsb") {
throw xlnt::exception(" xlnt does not support the .xlsb file format");
}
else if (file_format != "xlsx") {
throw xlnt::exception(" xlnt does not support the ."+file_format+ " file format");
}
}
else
{
throw xlnt::exception("file has no extension .xlsx");
}
return load(path(filename), password); return load(path(filename), password);
} }
@ -891,9 +923,21 @@ void workbook::save(std::vector<std::uint8_t> &data) const
save(data_stream); save(data_stream);
} }
void workbook::save(std::vector<std::uint8_t> &data, const std::string &password) const
{
xlnt::detail::vector_ostreambuf data_buffer(data);
std::ostream data_stream(&data_buffer);
save(data_stream, password);
}
void workbook::save(const std::string &filename) const void workbook::save(const std::string &filename) const
{ {
return save(path(filename)); save(path(filename));
}
void workbook::save(const std::string &filename, const std::string &password) const
{
save(path(filename), password);
} }
void workbook::save(const path &filename) const void workbook::save(const path &filename) const
@ -903,27 +947,34 @@ void workbook::save(const path &filename) const
save(file_stream); save(file_stream);
} }
void workbook::save(const path &filename, const std::string &password) const
{
std::ofstream file_stream;
open_stream(file_stream, filename.string());
save(file_stream, password);
}
void workbook::save(std::ostream &stream) const void workbook::save(std::ostream &stream) const
{ {
detail::xlsx_producer producer(*this); detail::xlsx_producer producer(*this);
producer.write(stream); producer.write(stream);
} }
void workbook::save(std::ostream &stream, const std::string &password) void workbook::save(std::ostream &stream, const std::string &password) const
{ {
detail::xlsx_producer producer(*this); detail::xlsx_producer producer(*this);
producer.write(stream, password); producer.write(stream, password);
} }
#ifdef _MSC_VER #ifdef _MSC_VER
void workbook::save(const std::wstring &filename) void workbook::save(const std::wstring &filename) const
{ {
std::ofstream file_stream; std::ofstream file_stream;
open_stream(file_stream, filename); open_stream(file_stream, filename);
save(file_stream); save(file_stream);
} }
void workbook::save(const std::wstring &filename, const std::string &password) void workbook::save(const std::wstring &filename, const std::string &password) const
{ {
std::ofstream file_stream; std::ofstream file_stream;
open_stream(file_stream, filename); open_stream(file_stream, filename);
@ -1402,33 +1453,21 @@ std::size_t workbook::rup_build() const
return d_->file_version_.get().rup_build; return d_->file_version_.get().rup_build;
} }
/// <summary>
///
/// </summary>
bool workbook::has_calculation_properties() const bool workbook::has_calculation_properties() const
{ {
return d_->calculation_properties_.is_set(); return d_->calculation_properties_.is_set();
} }
/// <summary>
///
/// </summary>
class calculation_properties workbook::calculation_properties() const class calculation_properties workbook::calculation_properties() const
{ {
return d_->calculation_properties_.get(); return d_->calculation_properties_.get();
} }
/// <summary>
///
/// </summary>
void workbook::calculation_properties(const class calculation_properties &props) void workbook::calculation_properties(const class calculation_properties &props)
{ {
d_->calculation_properties_ = props; d_->calculation_properties_ = props;
} }
/// <summary>
/// Removes calcChain part from manifest if no formulae remain in workbook.
/// </summary>
void workbook::garbage_collect_formulae() void workbook::garbage_collect_formulae()
{ {
auto any_with_formula = false; auto any_with_formula = false;

View File

@ -222,7 +222,8 @@ public:
xlnt::workbook wb; xlnt::workbook wb;
auto ws = wb.active_sheet(); auto ws = wb.active_sheet();
ws.cell("A1").hyperlink("http://test.com"); ws.cell("A1").hyperlink("http://test.com");
TS_ASSERT_EQUALS("http://test.com", ws.cell("A1").value<std::string>()); TS_ASSERT_EQUALS(ws.cell("A1").hyperlink(), "http://test.com");
TS_ASSERT_EQUALS(ws.cell("A1").value<std::string>(), "");
ws.cell("A1").value("test"); ws.cell("A1").value("test");
TS_ASSERT_EQUALS("test", ws.cell("A1").value<std::string>()); TS_ASSERT_EQUALS("test", ws.cell("A1").value<std::string>());
TS_ASSERT_EQUALS(ws.cell("A1").hyperlink(), "http://test.com"); TS_ASSERT_EQUALS(ws.cell("A1").hyperlink(), "http://test.com");

View File

@ -1010,6 +1010,11 @@ void worksheet::register_comments_in_manifest()
workbook().register_worksheet_part(*this, relationship_type::comments); workbook().register_worksheet_part(*this, relationship_type::comments);
} }
void worksheet::register_calc_chain_in_manifest()
{
workbook().register_workbook_part(relationship_type::calculation_chain);
}
bool worksheet::has_header_footer() const bool worksheet::has_header_footer() const
{ {
return d_->header_footer_.is_set(); return d_->header_footer_.is_set();

View File

@ -16,6 +16,8 @@ public:
{ {
// content types are stored in unordered maps, too complicated to compare // content types are stored in unordered maps, too complicated to compare
if (content_type == "[Content_Types].xml") return true; if (content_type == "[Content_Types].xml") return true;
// calcChain is optional
if (content_type == "application/vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml") return true;
auto is_xml = (content_type.substr(0, 12) == "application/" auto is_xml = (content_type.substr(0, 12) == "application/"
&& content_type.substr(content_type.size() - 4) == "+xml") && content_type.substr(content_type.size() - 4) == "+xml")