From 767d498dac7dccb993f4c3b43ee1f7da6575986b Mon Sep 17 00:00:00 2001 From: Thomas Fussell Date: Fri, 17 Feb 2017 23:11:06 -0600 Subject: [PATCH] separate crypto_helper into header file, fix lots of stuff --- include/xlnt/cell/cell.hpp | 15 +- include/xlnt/workbook/workbook.hpp | 10 +- include/xlnt/worksheet/worksheet.hpp | 5 + source/cell/cell.cpp | 18 +- source/cell/tests/test_cell.hpp | 2 +- source/detail/workbook_impl.hpp | 6 +- source/detail/xlsx_crypto.cpp | 1128 +++++++++---------- source/detail/xlsx_crypto.hpp | 162 +++ source/detail/xlsx_producer.cpp | 3 +- source/detail/zstream.cpp | 28 +- source/workbook/tests/test_produce_xlsx.hpp | 17 +- source/workbook/tests/test_round_trip.hpp | 60 +- source/workbook/workbook.cpp | 171 +-- source/worksheet/tests/test_worksheet.hpp | 3 +- source/worksheet/worksheet.cpp | 5 + tests/helpers/xml_helper.hpp | 4 +- 16 files changed, 905 insertions(+), 732 deletions(-) create mode 100644 source/detail/xlsx_crypto.hpp diff --git a/include/xlnt/cell/cell.hpp b/include/xlnt/cell/cell.hpp index 89a1c0ed..bd555336 100644 --- a/include/xlnt/cell/cell.hpp +++ b/include/xlnt/cell/cell.hpp @@ -183,9 +183,20 @@ public: std::string hyperlink() const; /// - /// 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. /// - void hyperlink(const std::string &value); + void hyperlink(const std::string &url); + + /// + /// 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. + /// + void hyperlink(const std::string &url, const std::string &display); + + /// + /// Adds an internal hyperlink to this cell pointing to the given cell. + /// + void hyperlink(xlnt::cell target); /// /// Returns true if this cell has a hyperlink set. diff --git a/include/xlnt/workbook/workbook.hpp b/include/xlnt/workbook/workbook.hpp index d7b8f29d..d4f00c06 100644 --- a/include/xlnt/workbook/workbook.hpp +++ b/include/xlnt/workbook/workbook.hpp @@ -450,20 +450,20 @@ public: /// Serializes the workbook into an XLSX file encrypted with the given password /// and loads the bytes into a file named filename. /// - void save(const std::string &filename, const std::string &password); + void save(const std::string &filename, const std::string &password) const; #ifdef _MSC_VER /// /// Serializes the workbook into an XLSX file and saves the data into a file /// named filename. /// - void save(const std::wstring &filename); + void save(const std::wstring &filename) const; /// /// Serializes the workbook into an XLSX file encrypted with the given password /// and loads the bytes into a file named filename. /// - void save(const std::wstring &filename, const std::string &password); + void save(const std::wstring &filename, const std::string &password) const; #endif /// @@ -476,7 +476,7 @@ public: /// Serializes the workbook into an XLSX file encrypted with the given password /// and loads the bytes into a file named filename. /// - void save(const xlnt::path &filename, const std::string &password); + void save(const xlnt::path &filename, const std::string &password) const; /// /// 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 /// and loads the bytes into the given stream. /// - void save(std::ostream &stream, const std::string &password); + void save(std::ostream &stream, const std::string &password) const; /// /// Interprets byte vector data as an XLSX file and sets the content of this diff --git a/include/xlnt/worksheet/worksheet.hpp b/include/xlnt/worksheet/worksheet.hpp index b731301c..7a231da9 100644 --- a/include/xlnt/worksheet/worksheet.hpp +++ b/include/xlnt/worksheet/worksheet.hpp @@ -742,6 +742,11 @@ private: /// void register_comments_in_manifest(); + /// + /// Add the calcChain part to the workbook if it doesn't already exist. + /// + void register_calc_chain_in_manifest(); + /// /// Removes calcChain part from manifest if no formulae remain in workbook. /// diff --git a/source/cell/cell.cpp b/source/cell/cell.cpp index a53a9fb4..0dd78fd0 100644 --- a/source/cell/cell.cpp +++ b/source/cell/cell.cpp @@ -446,18 +446,24 @@ void cell::hyperlink(const std::string &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) { if (formula.empty()) { - throw invalid_parameter(); + return clear_formula(); } if (formula[0] == '=') @@ -468,6 +474,8 @@ void cell::formula(const std::string &formula) { d_->formula_ = formula; } + + worksheet().register_calc_chain_in_manifest(); } bool cell::has_formula() const diff --git a/source/cell/tests/test_cell.hpp b/source/cell/tests/test_cell.hpp index 0e4a6102..81140c42 100644 --- a/source/cell/tests/test_cell.hpp +++ b/source/cell/tests/test_cell.hpp @@ -140,7 +140,7 @@ public: auto cell = ws.cell(xlnt::cell_reference(1, 1)); TS_ASSERT(!cell.has_formula()); - TS_ASSERT_THROWS(cell.formula(""), xlnt::invalid_parameter); + TS_ASSERT_THROWS_NOTHING(cell.formula("")); TS_ASSERT(!cell.has_formula()); cell.formula("=42"); TS_ASSERT(cell.has_formula()); diff --git a/source/detail/workbook_impl.hpp b/source/detail/workbook_impl.hpp index 8c37fec8..68d06a33 100644 --- a/source/detail/workbook_impl.hpp +++ b/source/detail/workbook_impl.hpp @@ -95,9 +95,9 @@ struct workbook_impl optional theme_; std::unordered_map> images_; - std::unordered_map> core_properties_; - std::unordered_map> extended_properties_; - std::unordered_map custom_properties_; + std::vector> core_properties_; + std::vector> extended_properties_; + std::vector> custom_properties_; std::unordered_map sheet_title_rel_id_map_; diff --git a/source/detail/xlsx_crypto.cpp b/source/detail/xlsx_crypto.cpp index 8282b2d5..790b5d26 100644 --- a/source/detail/xlsx_crypto.cpp +++ b/source/detail/xlsx_crypto.cpp @@ -21,37 +21,20 @@ // @license: http://www.opensource.org/licenses/mit-license.php // @author: see AUTHORS file -#include +#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +namespace { -namespace xlnt { -namespace detail { - -enum class hash_algorithm +template +auto read_int(std::size_t &index, const std::vector &raw_data) { - sha1, - sha256, - sha384, - sha512, - md5, - md4, - md2, - ripemd128, - ripemd160, - whirlpool -}; + auto result = *reinterpret_cast(&raw_data[index]); + index += sizeof(T); -} // namespace detail -} // namespace xlnt + return result; +} + +} // namespace namespace xml { @@ -116,654 +99,571 @@ struct value_traits namespace xlnt { namespace detail { -struct crypto_helper +std::vector crypto_helper::aes( + const std::vector &key, + const std::vector &iv, + const std::vector &source, + cipher_chaining chaining, cipher_direction direction) { - static const std::size_t segment_length; + std::vector destination(source.size(), 0); - enum class cipher_algorithm + if (direction == cipher_direction::encryption && chaining == cipher_chaining::cbc) { - aes, - rc2, - rc4, - des, - desx, - triple_des, - triple_des_112 - }; + CryptoPP::AES::Encryption aesEncryption(key.data(), key.size()); + CryptoPP::CBC_Mode_ExternalCipher::Encryption cbcEncryption(aesEncryption, iv.data()); - enum class cipher_chaining + CryptoPP::ArraySource as( + source.data(), source.size(), true, new CryptoPP::StreamTransformationFilter(cbcEncryption, + new CryptoPP::ArraySink(destination.data(), destination.size()), + CryptoPP::BlockPaddingSchemeDef::NO_PADDING)); + } + else if (direction == cipher_direction::decryption && chaining == cipher_chaining::cbc) { - ecb, // electronic code book - cbc, // cipher block chaining - cfb // cipher feedback chaining - }; + CryptoPP::AES::Decryption aesDecryption(key.data(), key.size()); + CryptoPP::CBC_Mode_ExternalCipher::Decryption cbcDecryption(aesDecryption, iv.data()); - enum class cipher_direction + CryptoPP::ArraySource as( + source.data(), source.size(), true, new CryptoPP::StreamTransformationFilter(cbcDecryption, + new CryptoPP::ArraySink(destination.data(), destination.size()), + CryptoPP::BlockPaddingSchemeDef::NO_PADDING)); + } + else if (direction == cipher_direction::encryption && chaining == cipher_chaining::ecb) { - encryption, - decryption - }; + CryptoPP::AES::Encryption aesEncryption(key.data(), key.size()); + CryptoPP::ECB_Mode_ExternalCipher::Encryption cbcEncryption(aesEncryption, iv.data()); - static std::vector aes(const std::vector &key, const std::vector &iv, - const std::vector &source, cipher_chaining chaining, cipher_direction direction) + CryptoPP::ArraySource as( + source.data(), source.size(), true, new CryptoPP::StreamTransformationFilter(cbcEncryption, + new CryptoPP::ArraySink(destination.data(), destination.size()), + CryptoPP::BlockPaddingSchemeDef::NO_PADDING)); + } + else if (direction == cipher_direction::decryption && chaining == cipher_chaining::ecb) { - std::vector destination(source.size(), 0); + CryptoPP::AES::Decryption aesDecryption(key.data(), key.size()); + CryptoPP::ECB_Mode_ExternalCipher::Decryption cbcDecryption(aesDecryption, iv.data()); - if (direction == cipher_direction::encryption && chaining == cipher_chaining::cbc) - { - CryptoPP::AES::Encryption aesEncryption(key.data(), key.size()); - CryptoPP::CBC_Mode_ExternalCipher::Encryption cbcEncryption(aesEncryption, iv.data()); - - CryptoPP::ArraySource as( - source.data(), source.size(), true, new CryptoPP::StreamTransformationFilter(cbcEncryption, - new CryptoPP::ArraySink(destination.data(), destination.size()), - CryptoPP::BlockPaddingSchemeDef::NO_PADDING)); - } - else if (direction == cipher_direction::decryption && chaining == cipher_chaining::cbc) - { - CryptoPP::AES::Decryption aesDecryption(key.data(), key.size()); - CryptoPP::CBC_Mode_ExternalCipher::Decryption cbcDecryption(aesDecryption, iv.data()); - - CryptoPP::ArraySource as( - source.data(), source.size(), true, new CryptoPP::StreamTransformationFilter(cbcDecryption, - new CryptoPP::ArraySink(destination.data(), destination.size()), - CryptoPP::BlockPaddingSchemeDef::NO_PADDING)); - } - else if (direction == cipher_direction::encryption && chaining == cipher_chaining::ecb) - { - CryptoPP::AES::Encryption aesEncryption(key.data(), key.size()); - CryptoPP::ECB_Mode_ExternalCipher::Encryption cbcEncryption(aesEncryption, iv.data()); - - CryptoPP::ArraySource as( - source.data(), source.size(), true, new CryptoPP::StreamTransformationFilter(cbcEncryption, - new CryptoPP::ArraySink(destination.data(), destination.size()), - CryptoPP::BlockPaddingSchemeDef::NO_PADDING)); - } - else if (direction == cipher_direction::decryption && chaining == cipher_chaining::ecb) - { - CryptoPP::AES::Decryption aesDecryption(key.data(), key.size()); - CryptoPP::ECB_Mode_ExternalCipher::Decryption cbcDecryption(aesDecryption, iv.data()); - - CryptoPP::ArraySource as( - source.data(), source.size(), true, new CryptoPP::StreamTransformationFilter(cbcDecryption, - new CryptoPP::ArraySink(destination.data(), destination.size()), - CryptoPP::BlockPaddingSchemeDef::NO_PADDING)); - } - - return destination; + CryptoPP::ArraySource as( + source.data(), source.size(), true, new CryptoPP::StreamTransformationFilter(cbcDecryption, + new CryptoPP::ArraySink(destination.data(), destination.size()), + CryptoPP::BlockPaddingSchemeDef::NO_PADDING)); } - static std::vector decode_base64(const std::string &encoded) + return destination; +} + +std::vector crypto_helper::decode_base64(const std::string &encoded) +{ + CryptoPP::Base64Decoder decoder; + decoder.Put(reinterpret_cast(encoded.data()), encoded.size()); + decoder.MessageEnd(); + + std::vector decoded(decoder.MaxRetrievable(), 0); + decoder.Get(decoded.data(), decoded.size()); + + return decoded; +} + +std::string crypto_helper::encode_base64(const std::vector &decoded) +{ + CryptoPP::Base64Decoder encoder; + encoder.Put(reinterpret_cast(decoded.data()), decoded.size()); + encoder.MessageEnd(); + + std::vector encoded(encoder.MaxRetrievable(), 0); + encoder.Get(encoded.data(), encoded.size()); + + return std::string(encoded.begin(), encoded.end()); +} + +std::vector crypto_helper::hash(hash_algorithm algorithm, const std::vector &input) +{ + std::vector digest; + + if (algorithm == hash_algorithm::sha512) { - CryptoPP::Base64Decoder decoder; - decoder.Put(reinterpret_cast(encoded.data()), encoded.size()); - decoder.MessageEnd(); - - std::vector decoded(decoder.MaxRetrievable(), 0); - decoder.Get(decoded.data(), decoded.size()); - - return decoded; + CryptoPP::SHA512 sha512; + digest.resize(CryptoPP::SHA512::DIGESTSIZE, 0); + sha512.CalculateDigest(digest.data(), input.data(), input.size()); + } + else if (algorithm == hash_algorithm::sha1) + { + CryptoPP::SHA1 sha1; + digest.resize(CryptoPP::SHA1::DIGESTSIZE, 0); + sha1.CalculateDigest(digest.data(), input.data(), input.size()); } - static std::string encode_base64(const std::vector &decoded) + return digest; +} + +std::vector crypto_helper::file(POLE::Storage &storage, const std::string &name) +{ + POLE::Stream stream(&storage, name.c_str()); + if (stream.fail()) return {}; + std::vector bytes(stream.size(), 0); + stream.read(bytes.data(), static_cast(bytes.size())); + return bytes; +} + +std::vector crypto_helper::decrypt_xlsx_standard( + const std::vector &encryption_info, + const std::string &password, + const std::vector &encrypted_package) +{ + std::size_t offset = 0; + + standard_encryption_info info; + + auto header_length = read_int(offset, encryption_info); + auto index_at_start = offset; + /*auto skip_flags = */ read_int(offset, encryption_info); + /*auto size_extra = */ read_int(offset, encryption_info); + auto alg_id = read_int(offset, encryption_info); + + if (alg_id == 0 || alg_id == 0x0000660E || alg_id == 0x0000660F || alg_id == 0x00006610) { - CryptoPP::Base64Decoder encoder; - encoder.Put(reinterpret_cast(decoded.data()), decoded.size()); - encoder.MessageEnd(); - - std::vector encoded(encoder.MaxRetrievable(), 0); - encoder.Get(encoded.data(), encoded.size()); - - return std::string(encoded.begin(), encoded.end()); + info.cipher = cipher_algorithm::aes; + } + else + { + throw xlnt::exception("invalid cipher algorithm"); } - static std::vector hash(hash_algorithm algorithm, const std::vector &input) + auto alg_id_hash = read_int(offset, encryption_info); + if (alg_id_hash != 0x00008004 && alg_id_hash == 0) { - std::vector digest; + throw xlnt::exception("invalid hash algorithm"); + } - if (algorithm == hash_algorithm::sha512) + info.key_bits = read_int(offset, encryption_info); + info.key_bytes = info.key_bits / 8; + + auto provider_type = read_int(offset, encryption_info); + if (provider_type != 0 && provider_type != 0x00000018) + { + throw xlnt::exception("invalid provider type"); + } + + read_int(offset, encryption_info); // reserved 1 + if (read_int(offset, encryption_info) != 0) // reserved 2 + { + throw xlnt::exception("invalid header"); + } + + const auto csp_name_length = header_length - (offset - index_at_start); + std::vector csp_name_wide( + reinterpret_cast(&*(encryption_info.begin() + static_cast(offset))), + reinterpret_cast( + &*(encryption_info.begin() + static_cast(offset + csp_name_length)))); + std::string csp_name(csp_name_wide.begin(), csp_name_wide.end() - 1); // without trailing null + if (csp_name != "Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)" + && csp_name != "Microsoft Enhanced RSA and AES Cryptographic Provider") + { + throw xlnt::exception("invalid cryptographic provider"); + } + offset += csp_name_length; + + const auto salt_size = read_int(offset, encryption_info); + std::vector salt(encryption_info.begin() + static_cast(offset), + encryption_info.begin() + static_cast(offset + salt_size)); + offset += salt_size; + + static const auto verifier_size = std::size_t(16); + std::vector verifier_hash_input(encryption_info.begin() + static_cast(offset), + encryption_info.begin() + static_cast(offset + verifier_size)); + offset += verifier_size; + + const auto verifier_hash_size = read_int(offset, encryption_info); + std::vector verifier_hash_value(encryption_info.begin() + static_cast(offset), + encryption_info.begin() + static_cast(offset + verifier_hash_size)); + offset += verifier_hash_size; + + // begin key generation algorithm + + // H_0 = H(salt + password) + auto salt_plus_password = salt; + std::vector password_wide(password.begin(), password.end()); + std::for_each(password_wide.begin(), password_wide.end(), [&salt_plus_password](std::uint16_t c) { + salt_plus_password.insert(salt_plus_password.end(), reinterpret_cast(&c), + reinterpret_cast(&c) + sizeof(std::uint16_t)); + }); + std::vector h_0 = hash(info.hash, salt_plus_password); + + // H_n = H(iterator + H_n-1) + std::vector iterator_plus_h_n(4, 0); + iterator_plus_h_n.insert(iterator_plus_h_n.end(), h_0.begin(), h_0.end()); + std::uint32_t &iterator = *reinterpret_cast(iterator_plus_h_n.data()); + std::vector h_n; + for (iterator = 0; iterator < info.spin_count; ++iterator) + { + h_n = hash(info.hash, iterator_plus_h_n); + std::copy(h_n.begin(), h_n.end(), iterator_plus_h_n.begin() + 4); + } + + // H_final = H(H_n + block) + auto h_n_plus_block = h_n; + const std::uint32_t block_number = 0; + h_n_plus_block.insert(h_n_plus_block.end(), reinterpret_cast(&block_number), + reinterpret_cast(&block_number) + sizeof(std::uint32_t)); + auto h_final = hash(info.hash, h_n_plus_block); + + // X1 = H(h_final ^ 0x36) + std::vector buffer(64, 0x36); + for (std::size_t i = 0; i < h_final.size(); ++i) + { + buffer[i] = static_cast(0x36 ^ h_final[i]); + } + auto X1 = hash(info.hash, buffer); + + // X2 = H(h_final ^ 0x5C) + buffer.assign(64, 0x5c); + for (std::size_t i = 0; i < h_final.size(); ++i) + { + buffer[i] = static_cast(0x5c ^ h_final[i]); + } + auto X2 = hash(info.hash, buffer); + + auto X3 = X1; + X3.insert(X3.end(), X2.begin(), X2.end()); + + auto key_derived = + std::vector(X3.begin(), X3.begin() + static_cast(info.key_bytes)); + + // todo: verify here + + std::size_t package_offset = 0; + auto decrypted_size = static_cast(read_int(package_offset, encrypted_package)); + auto decrypted = aes(key_derived, {}, + std::vector(encrypted_package.begin() + 8, encrypted_package.end()), + cipher_chaining::ecb, cipher_direction::decryption); + decrypted.resize(decrypted_size); + + return decrypted; +} + +crypto_helper::agile_encryption_info crypto_helper::generate_agile_encryption_info(const std::string &password) +{ + agile_encryption_info result; + result.key_data.salt_value.assign(password.begin(), password.end()); + return result; +} + +std::vector crypto_helper::write_agile_encryption_info(const std::string &password) +{ + static const auto &xmlns = xlnt::constants::ns("encryption"); + static const auto &xmlns_p = xlnt::constants::ns("encryption-password"); + + std::vector encryption_info; + xlnt::detail::vector_ostreambuf encryption_info_buffer(encryption_info); + std::ostream encryption_info_stream(&encryption_info_buffer); + xml::serializer serializer(encryption_info_stream, "EncryptionInfo"); + + agile_encryption_info result = generate_agile_encryption_info(password); + + serializer.start_element(xmlns, "encryption"); + + serializer.start_element(xmlns, "keyData"); + serializer.attribute("saltSize", result.key_data.salt_size); + serializer.attribute("blockSize", result.key_data.block_size); + serializer.attribute("keyBits", result.key_data.key_bits); + serializer.attribute("hashSize", result.key_data.hash_size); + serializer.attribute("cipherAlgorithm", result.key_data.cipher_algorithm); + serializer.attribute("cipherChaining", result.key_data.cipher_chaining); + serializer.attribute("hashAlgorithm", result.key_data.hash_algorithm); + serializer.attribute("saltValue", encode_base64(result.key_data.salt_value)); + serializer.end_element(xmlns, "keyData"); + + serializer.start_element(xmlns, "dataIntegrity"); + serializer.attribute("encryptedHmacKey", encode_base64(result.data_integrity.hmac_key)); + serializer.attribute("encryptedHmacValue", encode_base64(result.data_integrity.hmac_value)); + serializer.end_element(xmlns, "dataIntegrity"); + + serializer.start_element(xmlns, "keyEncryptors"); + serializer.start_element(xmlns, "keyEncryptor"); + serializer.attribute("uri", ""); + serializer.start_element(xmlns_p, "encryptedKey"); + serializer.attribute("spinCount", result.key_encryptor.spin_count); + serializer.attribute("saltSize", result.key_encryptor.salt_size); + serializer.attribute("blockSize", result.key_encryptor.block_size); + serializer.attribute("keyBits", result.key_encryptor.key_bits); + serializer.attribute("hashSize", result.key_encryptor.hash_size); + serializer.attribute("cipherAlgorithm", result.key_encryptor.cipher_algorithm); + serializer.attribute("cipherChaining", result.key_encryptor.cipher_chaining); + serializer.attribute("hashAlgorithm", result.key_encryptor.hash); + serializer.attribute("saltValue", encode_base64(result.key_encryptor.salt_value)); + serializer.attribute("encryptedVerifierHashInput", encode_base64(result.key_encryptor.verifier_hash_input)); + serializer.attribute("encryptedVerifierHashValue", encode_base64(result.key_encryptor.verifier_hash_value)); + serializer.attribute("encryptedKeyValue", encode_base64(result.key_encryptor.encrypted_key_value)); + serializer.end_element(xmlns_p, "encryptedKey"); + serializer.end_element(xmlns, "keyEncryptor"); + serializer.end_element(xmlns, "keyEncryptors"); + + serializer.end_element(xmlns, "encryption"); + + return encryption_info; +} + +std::vector crypto_helper::decrypt_xlsx_agile( + const std::vector &encryption_info, + const std::string &password, + const std::vector &encrypted_package) +{ + static const auto &xmlns = xlnt::constants::ns("encryption"); + static const auto &xmlns_p = xlnt::constants::ns("encryption-password"); + // static const auto &xmlns_c = xlnt::constants::namespace_("encryption-certificate"); + + agile_encryption_info result; + + xml::parser parser(encryption_info.data(), encryption_info.size(), "EncryptionInfo"); + + parser.next_expect(xml::parser::event_type::start_element, xmlns, "encryption"); + + parser.next_expect(xml::parser::event_type::start_element, xmlns, "keyData"); + result.key_data.salt_size = parser.attribute("saltSize"); + result.key_data.block_size = parser.attribute("blockSize"); + result.key_data.key_bits = parser.attribute("keyBits"); + result.key_data.hash_size = parser.attribute("hashSize"); + result.key_data.cipher_algorithm = parser.attribute("cipherAlgorithm"); + result.key_data.cipher_chaining = parser.attribute("cipherChaining"); + result.key_data.hash_algorithm = parser.attribute("hashAlgorithm"); + result.key_data.salt_value = decode_base64(parser.attribute("saltValue")); + parser.next_expect(xml::parser::event_type::end_element, xmlns, "keyData"); + + parser.next_expect(xml::parser::event_type::start_element, xmlns, "dataIntegrity"); + result.data_integrity.hmac_key = decode_base64(parser.attribute("encryptedHmacKey")); + result.data_integrity.hmac_value = decode_base64(parser.attribute("encryptedHmacValue")); + parser.next_expect(xml::parser::event_type::end_element, xmlns, "dataIntegrity"); + + parser.next_expect(xml::parser::event_type::start_element, xmlns, "keyEncryptors"); + parser.next_expect(xml::parser::event_type::start_element, xmlns, "keyEncryptor"); + parser.attribute("uri"); + bool any_password_key = false; + + while (parser.peek() != xml::parser::event_type::end_element) + { + parser.next_expect(xml::parser::event_type::start_element); + + if (parser.namespace_() == xmlns_p && parser.name() == "encryptedKey") { - CryptoPP::SHA512 sha512; - digest.resize(CryptoPP::SHA512::DIGESTSIZE, 0); - sha512.CalculateDigest(digest.data(), input.data(), input.size()); - } - else if (algorithm == hash_algorithm::sha1) - { - CryptoPP::SHA1 sha1; - digest.resize(CryptoPP::SHA1::DIGESTSIZE, 0); - sha1.CalculateDigest(digest.data(), input.data(), input.size()); - } + any_password_key = true; + result.key_encryptor.spin_count = parser.attribute("spinCount"); + result.key_encryptor.salt_size = parser.attribute("saltSize"); + result.key_encryptor.block_size = parser.attribute("blockSize"); + result.key_encryptor.key_bits = parser.attribute("keyBits"); + result.key_encryptor.hash_size = parser.attribute("hashSize"); + result.key_encryptor.cipher_algorithm = parser.attribute("cipherAlgorithm"); + result.key_encryptor.cipher_chaining = parser.attribute("cipherChaining"); - return digest; - } + auto hash_algorithm_string = parser.attribute("hashAlgorithm"); + if (hash_algorithm_string == "SHA512") + { + result.key_encryptor.hash = hash_algorithm::sha512; + } + else if (hash_algorithm_string == "SHA1") + { + result.key_encryptor.hash = hash_algorithm::sha1; + } + else if (hash_algorithm_string == "SHA256") + { + result.key_encryptor.hash = hash_algorithm::sha256; + } + else if (hash_algorithm_string == "SHA384") + { + result.key_encryptor.hash = hash_algorithm::sha384; + } - static std::vector file(POLE::Storage &storage, const std::string &name) - { - POLE::Stream stream(&storage, name.c_str()); - if (stream.fail()) return {}; - std::vector bytes(stream.size(), 0); - stream.read(bytes.data(), static_cast(bytes.size())); - return bytes; - } - - template - static auto read_int(std::size_t &index, const std::vector &raw_data) - { - auto result = *reinterpret_cast(&raw_data[index]); - 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 salt_value; - std::vector verifier_hash_input; - std::vector verifier_hash_value; - std::vector encrypted_key_value; - }; - - static std::vector decrypt_xlsx_standard(const std::vector &encryption_info, - const std::string &password, const std::vector &encrypted_package) - { - std::size_t offset = 0; - - standard_encryption_info info; - - auto header_length = read_int(offset, encryption_info); - auto index_at_start = offset; - /*auto skip_flags = */ read_int(offset, encryption_info); - /*auto size_extra = */ read_int(offset, encryption_info); - auto alg_id = read_int(offset, encryption_info); - - if (alg_id == 0 || alg_id == 0x0000660E || alg_id == 0x0000660F || alg_id == 0x00006610) - { - info.cipher = cipher_algorithm::aes; + result.key_encryptor.salt_value = decode_base64(parser.attribute("saltValue")); + result.key_encryptor.verifier_hash_input = + decode_base64(parser.attribute("encryptedVerifierHashInput")); + result.key_encryptor.verifier_hash_value = + decode_base64(parser.attribute("encryptedVerifierHashValue")); + result.key_encryptor.encrypted_key_value = decode_base64(parser.attribute("encryptedKeyValue")); } else { - throw xlnt::exception("invalid cipher algorithm"); + throw xlnt::unsupported("other encryption key types not supported"); } - auto alg_id_hash = read_int(offset, encryption_info); - if (alg_id_hash != 0x00008004 && alg_id_hash == 0) - { - throw xlnt::exception("invalid hash algorithm"); - } - - info.key_bits = read_int(offset, encryption_info); - info.key_bytes = info.key_bits / 8; - - auto provider_type = read_int(offset, encryption_info); - if (provider_type != 0 && provider_type != 0x00000018) - { - throw xlnt::exception("invalid provider type"); - } - - read_int(offset, encryption_info); // reserved 1 - if (read_int(offset, encryption_info) != 0) // reserved 2 - { - throw xlnt::exception("invalid header"); - } - - const auto csp_name_length = header_length - (offset - index_at_start); - std::vector csp_name_wide( - reinterpret_cast(&*(encryption_info.begin() + static_cast(offset))), - reinterpret_cast( - &*(encryption_info.begin() + static_cast(offset + csp_name_length)))); - std::string csp_name(csp_name_wide.begin(), csp_name_wide.end() - 1); // without trailing null - if (csp_name != "Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)" - && csp_name != "Microsoft Enhanced RSA and AES Cryptographic Provider") - { - throw xlnt::exception("invalid cryptographic provider"); - } - offset += csp_name_length; - - const auto salt_size = read_int(offset, encryption_info); - std::vector salt(encryption_info.begin() + static_cast(offset), - encryption_info.begin() + static_cast(offset + salt_size)); - offset += salt_size; - - static const auto verifier_size = std::size_t(16); - std::vector verifier_hash_input(encryption_info.begin() + static_cast(offset), - encryption_info.begin() + static_cast(offset + verifier_size)); - offset += verifier_size; - - const auto verifier_hash_size = read_int(offset, encryption_info); - std::vector verifier_hash_value(encryption_info.begin() + static_cast(offset), - encryption_info.begin() + static_cast(offset + verifier_hash_size)); - offset += verifier_hash_size; - - // begin key generation algorithm - - // H_0 = H(salt + password) - auto salt_plus_password = salt; - std::vector password_wide(password.begin(), password.end()); - std::for_each(password_wide.begin(), password_wide.end(), [&salt_plus_password](std::uint16_t c) { - salt_plus_password.insert(salt_plus_password.end(), reinterpret_cast(&c), - reinterpret_cast(&c) + sizeof(std::uint16_t)); - }); - std::vector h_0 = hash(info.hash, salt_plus_password); - - // H_n = H(iterator + H_n-1) - std::vector iterator_plus_h_n(4, 0); - iterator_plus_h_n.insert(iterator_plus_h_n.end(), h_0.begin(), h_0.end()); - std::uint32_t &iterator = *reinterpret_cast(iterator_plus_h_n.data()); - std::vector h_n; - for (iterator = 0; iterator < info.spin_count; ++iterator) - { - h_n = hash(info.hash, iterator_plus_h_n); - std::copy(h_n.begin(), h_n.end(), iterator_plus_h_n.begin() + 4); - } - - // H_final = H(H_n + block) - auto h_n_plus_block = h_n; - const std::uint32_t block_number = 0; - h_n_plus_block.insert(h_n_plus_block.end(), reinterpret_cast(&block_number), - reinterpret_cast(&block_number) + sizeof(std::uint32_t)); - auto h_final = hash(info.hash, h_n_plus_block); - - // X1 = H(h_final ^ 0x36) - std::vector buffer(64, 0x36); - for (std::size_t i = 0; i < h_final.size(); ++i) - { - buffer[i] = static_cast(0x36 ^ h_final[i]); - } - auto X1 = hash(info.hash, buffer); - - // X2 = H(h_final ^ 0x5C) - buffer.assign(64, 0x5c); - for (std::size_t i = 0; i < h_final.size(); ++i) - { - buffer[i] = static_cast(0x5c ^ h_final[i]); - } - auto X2 = hash(info.hash, buffer); - - auto X3 = X1; - X3.insert(X3.end(), X2.begin(), X2.end()); - - auto key_derived = - std::vector(X3.begin(), X3.begin() + static_cast(info.key_bytes)); - - // todo: verify here - - std::size_t package_offset = 0; - auto decrypted_size = static_cast(read_int(package_offset, encrypted_package)); - auto decrypted = - aes(key_derived, {}, std::vector(encrypted_package.begin() + 8, encrypted_package.end()), - cipher_chaining::ecb, cipher_direction::decryption); - decrypted.resize(decrypted_size); - - return decrypted; + parser.next_expect(xml::parser::event_type::end_element); } - struct agile_encryption_info + if (!any_password_key) { - // 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 salt_value; - } key_data; + throw "no password key in keyEncryptors"; + } - struct - { - std::vector hmac_key; - std::vector hmac_value; - } data_integrity; + parser.next_expect(xml::parser::event_type::end_element, xmlns, "keyEncryptor"); + parser.next_expect(xml::parser::event_type::end_element, xmlns, "keyEncryptors"); - 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 salt_value; - std::vector verifier_hash_input; - std::vector verifier_hash_value; - std::vector encrypted_key_value; - } key_encryptor; + parser.next_expect(xml::parser::event_type::end_element, xmlns, "encryption"); + + // begin key generation algorithm + + // H_0 = H(salt + password) + auto salt_plus_password = result.key_encryptor.salt_value; + std::vector password_wide(password.begin(), password.end()); + + std::for_each(password_wide.begin(), password_wide.end(), [&salt_plus_password](std::uint16_t c) { + salt_plus_password.insert(salt_plus_password.end(), reinterpret_cast(&c), + reinterpret_cast(&c) + sizeof(std::uint16_t)); + }); + + auto h_0 = hash(result.key_encryptor.hash, salt_plus_password); + + // H_n = H(iterator + H_n-1) + std::vector iterator_plus_h_n(4, 0); + iterator_plus_h_n.insert(iterator_plus_h_n.end(), h_0.begin(), h_0.end()); + std::uint32_t &iterator = *reinterpret_cast(iterator_plus_h_n.data()); + std::vector h_n; + + for (iterator = 0; iterator < result.key_encryptor.spin_count; ++iterator) + { + h_n = hash(result.key_encryptor.hash, iterator_plus_h_n); + std::copy(h_n.begin(), h_n.end(), iterator_plus_h_n.begin() + 4); + } + + static const std::size_t block_size = 8; + + auto calculate_block = [&result](const std::vector &raw_key, + const std::array &block, const std::vector &encrypted) { + auto combined = raw_key; + combined.insert(combined.end(), block.begin(), block.end()); + auto key = hash(result.key_encryptor.hash, combined); + key.resize(result.key_encryptor.key_bits / 8); + return aes(key, result.key_encryptor.salt_value, encrypted, + cipher_chaining::cbc, cipher_direction::decryption); }; - static agile_encryption_info generate_agile_encryption_info(const std::string &password) + const std::array input_block_key = {{0xfe, 0xa7, 0xd2, 0x76, 0x3b, 0x4b, 0x9e, 0x79}}; + auto hash_input = calculate_block(h_n, input_block_key, result.key_encryptor.verifier_hash_input); + auto calculated_verifier = hash(result.key_encryptor.hash, hash_input); + + const std::array verifier_block_key = { + {0xd7, 0xaa, 0x0f, 0x6d, 0x30, 0x61, 0x34, 0x4e}}; + auto expected_verifier = calculate_block(h_n, verifier_block_key, result.key_encryptor.verifier_hash_value); + expected_verifier.resize(calculated_verifier.size()); + + if (calculated_verifier.size() != expected_verifier.size() + || std::mismatch(calculated_verifier.begin(), calculated_verifier.end(), expected_verifier.begin(), + expected_verifier.end()) + != std::make_pair(calculated_verifier.end(), expected_verifier.end())) { - agile_encryption_info result; - result.key_data.salt_value.assign(password.begin(), password.end()); - return result; + throw xlnt::exception("bad password"); } - static std::vector write_agile_encryption_info(const std::string &password) + const std::array key_value_block_key = { + {0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6}}; + auto key = calculate_block(h_n, key_value_block_key, result.key_encryptor.encrypted_key_value); + + auto salt_size = result.key_data.salt_size; + auto salt_with_block_key = result.key_data.salt_value; + salt_with_block_key.resize(salt_size + sizeof(std::uint32_t), 0); + + auto &segment = *reinterpret_cast(salt_with_block_key.data() + salt_size); + auto total_size = static_cast(*reinterpret_cast(encrypted_package.data())); + + std::vector encrypted_segment(segment_length, 0); + std::vector decrypted_package; + decrypted_package.reserve(encrypted_package.size() - 8); + + for (std::size_t i = 8; i < encrypted_package.size(); i += segment_length) { - static const auto &xmlns = xlnt::constants::ns("encryption"); - static const auto &xmlns_p = xlnt::constants::ns("encryption-password"); + auto iv = hash(result.key_encryptor.hash, salt_with_block_key); + iv.resize(16); - std::vector encryption_info; - xlnt::detail::vector_ostreambuf encryption_info_buffer(encryption_info); - std::ostream encryption_info_stream(&encryption_info_buffer); - xml::serializer serializer(encryption_info_stream, "EncryptionInfo"); + auto segment_begin = encrypted_package.begin() + static_cast(i); + auto current_segment_length = std::min(segment_length, encrypted_package.size() - i); + auto segment_end = encrypted_package.begin() + static_cast(i + current_segment_length); + encrypted_segment.assign(segment_begin, segment_end); + auto decrypted_segment = + aes(key, iv, encrypted_segment, cipher_chaining::cbc, cipher_direction::decryption); + decrypted_segment.resize(current_segment_length); - agile_encryption_info result = generate_agile_encryption_info(password); + decrypted_package.insert(decrypted_package.end(), decrypted_segment.begin(), decrypted_segment.end()); - serializer.start_element(xmlns, "encryption"); - - serializer.start_element(xmlns, "keyData"); - serializer.attribute("saltSize", result.key_data.salt_size); - serializer.attribute("blockSize", result.key_data.block_size); - serializer.attribute("keyBits", result.key_data.key_bits); - serializer.attribute("hashSize", result.key_data.hash_size); - serializer.attribute("cipherAlgorithm", result.key_data.cipher_algorithm); - serializer.attribute("cipherChaining", result.key_data.cipher_chaining); - serializer.attribute("hashAlgorithm", result.key_data.hash_algorithm); - serializer.attribute("saltValue", encode_base64(result.key_data.salt_value)); - serializer.end_element(xmlns, "keyData"); - - serializer.start_element(xmlns, "dataIntegrity"); - serializer.attribute("encryptedHmacKey", encode_base64(result.data_integrity.hmac_key)); - serializer.attribute("encryptedHmacValue", encode_base64(result.data_integrity.hmac_value)); - serializer.end_element(xmlns, "dataIntegrity"); - - serializer.start_element(xmlns, "keyEncryptors"); - serializer.start_element(xmlns, "keyEncryptor"); - serializer.attribute("uri", ""); - serializer.start_element(xmlns_p, "encryptedKey"); - serializer.attribute("spinCount", result.key_encryptor.spin_count); - serializer.attribute("saltSize", result.key_encryptor.salt_size); - serializer.attribute("blockSize", result.key_encryptor.block_size); - serializer.attribute("keyBits", result.key_encryptor.key_bits); - serializer.attribute("hashSize", result.key_encryptor.hash_size); - serializer.attribute("cipherAlgorithm", result.key_encryptor.cipher_algorithm); - serializer.attribute("cipherChaining", result.key_encryptor.cipher_chaining); - serializer.attribute("hashAlgorithm", result.key_encryptor.hash); - serializer.attribute("saltValue", encode_base64(result.key_encryptor.salt_value)); - serializer.attribute("encryptedVerifierHashInput", encode_base64(result.key_encryptor.verifier_hash_input)); - serializer.attribute("encryptedVerifierHashValue", encode_base64(result.key_encryptor.verifier_hash_value)); - serializer.attribute("encryptedKeyValue", encode_base64(result.key_encryptor.encrypted_key_value)); - serializer.end_element(xmlns_p, "encryptedKey"); - serializer.end_element(xmlns, "keyEncryptor"); - serializer.end_element(xmlns, "keyEncryptors"); - - serializer.end_element(xmlns, "encryption"); - - return encryption_info; + ++segment; } - static std::vector decrypt_xlsx_agile(const std::vector &encryption_info, - const std::string &password, const std::vector &encrypted_package) + decrypted_package.resize(total_size); + + return decrypted_package; +} + +std::vector crypto_helper::decrypt_xlsx( + const std::vector &bytes, const std::string &password) +{ + if (bytes.empty()) { - static const auto &xmlns = xlnt::constants::ns("encryption"); - static const auto &xmlns_p = xlnt::constants::ns("encryption-password"); - // static const auto &xmlns_c = xlnt::constants::namespace_("encryption-certificate"); - - agile_encryption_info result; - - xml::parser parser(encryption_info.data(), encryption_info.size(), "EncryptionInfo"); - - parser.next_expect(xml::parser::event_type::start_element, xmlns, "encryption"); - - parser.next_expect(xml::parser::event_type::start_element, xmlns, "keyData"); - result.key_data.salt_size = parser.attribute("saltSize"); - result.key_data.block_size = parser.attribute("blockSize"); - result.key_data.key_bits = parser.attribute("keyBits"); - result.key_data.hash_size = parser.attribute("hashSize"); - result.key_data.cipher_algorithm = parser.attribute("cipherAlgorithm"); - result.key_data.cipher_chaining = parser.attribute("cipherChaining"); - result.key_data.hash_algorithm = parser.attribute("hashAlgorithm"); - result.key_data.salt_value = decode_base64(parser.attribute("saltValue")); - parser.next_expect(xml::parser::event_type::end_element, xmlns, "keyData"); - - parser.next_expect(xml::parser::event_type::start_element, xmlns, "dataIntegrity"); - result.data_integrity.hmac_key = decode_base64(parser.attribute("encryptedHmacKey")); - result.data_integrity.hmac_value = decode_base64(parser.attribute("encryptedHmacValue")); - parser.next_expect(xml::parser::event_type::end_element, xmlns, "dataIntegrity"); - - parser.next_expect(xml::parser::event_type::start_element, xmlns, "keyEncryptors"); - parser.next_expect(xml::parser::event_type::start_element, xmlns, "keyEncryptor"); - parser.attribute("uri"); - bool any_password_key = false; - - while (parser.peek() != xml::parser::event_type::end_element) - { - parser.next_expect(xml::parser::event_type::start_element); - - if (parser.namespace_() == xmlns_p && parser.name() == "encryptedKey") - { - any_password_key = true; - result.key_encryptor.spin_count = parser.attribute("spinCount"); - result.key_encryptor.salt_size = parser.attribute("saltSize"); - result.key_encryptor.block_size = parser.attribute("blockSize"); - result.key_encryptor.key_bits = parser.attribute("keyBits"); - result.key_encryptor.hash_size = parser.attribute("hashSize"); - result.key_encryptor.cipher_algorithm = parser.attribute("cipherAlgorithm"); - result.key_encryptor.cipher_chaining = parser.attribute("cipherChaining"); - - auto hash_algorithm_string = parser.attribute("hashAlgorithm"); - if (hash_algorithm_string == "SHA512") - { - result.key_encryptor.hash = hash_algorithm::sha512; - } - else if (hash_algorithm_string == "SHA1") - { - result.key_encryptor.hash = hash_algorithm::sha1; - } - else if (hash_algorithm_string == "SHA256") - { - result.key_encryptor.hash = hash_algorithm::sha256; - } - else if (hash_algorithm_string == "SHA384") - { - result.key_encryptor.hash = hash_algorithm::sha384; - } - - result.key_encryptor.salt_value = decode_base64(parser.attribute("saltValue")); - result.key_encryptor.verifier_hash_input = - decode_base64(parser.attribute("encryptedVerifierHashInput")); - result.key_encryptor.verifier_hash_value = - decode_base64(parser.attribute("encryptedVerifierHashValue")); - result.key_encryptor.encrypted_key_value = decode_base64(parser.attribute("encryptedKeyValue")); - } - else - { - throw xlnt::unsupported("other encryption key types not supported"); - } - - parser.next_expect(xml::parser::event_type::end_element); - } - - if (!any_password_key) - { - throw "no password key in keyEncryptors"; - } - - parser.next_expect(xml::parser::event_type::end_element, xmlns, "keyEncryptor"); - parser.next_expect(xml::parser::event_type::end_element, xmlns, "keyEncryptors"); - - parser.next_expect(xml::parser::event_type::end_element, xmlns, "encryption"); - - // begin key generation algorithm - - // H_0 = H(salt + password) - auto salt_plus_password = result.key_encryptor.salt_value; - std::vector password_wide(password.begin(), password.end()); - - std::for_each(password_wide.begin(), password_wide.end(), [&salt_plus_password](std::uint16_t c) { - salt_plus_password.insert(salt_plus_password.end(), reinterpret_cast(&c), - reinterpret_cast(&c) + sizeof(std::uint16_t)); - }); - - auto h_0 = hash(result.key_encryptor.hash, salt_plus_password); - - // H_n = H(iterator + H_n-1) - std::vector iterator_plus_h_n(4, 0); - iterator_plus_h_n.insert(iterator_plus_h_n.end(), h_0.begin(), h_0.end()); - std::uint32_t &iterator = *reinterpret_cast(iterator_plus_h_n.data()); - std::vector h_n; - - for (iterator = 0; iterator < result.key_encryptor.spin_count; ++iterator) - { - h_n = hash(result.key_encryptor.hash, iterator_plus_h_n); - std::copy(h_n.begin(), h_n.end(), iterator_plus_h_n.begin() + 4); - } - - static const std::size_t block_size = 8; - - auto calculate_block = [&result](const std::vector &raw_key, - const std::array &block, const std::vector &encrypted) { - auto combined = raw_key; - combined.insert(combined.end(), block.begin(), block.end()); - auto key = hash(result.key_encryptor.hash, combined); - key.resize(result.key_encryptor.key_bits / 8); - return aes( - key, result.key_encryptor.salt_value, encrypted, cipher_chaining::cbc, cipher_direction::decryption); - }; - - const std::array input_block_key = {{0xfe, 0xa7, 0xd2, 0x76, 0x3b, 0x4b, 0x9e, 0x79}}; - auto hash_input = calculate_block(h_n, input_block_key, result.key_encryptor.verifier_hash_input); - auto calculated_verifier = hash(result.key_encryptor.hash, hash_input); - - const std::array verifier_block_key = { - {0xd7, 0xaa, 0x0f, 0x6d, 0x30, 0x61, 0x34, 0x4e}}; - auto expected_verifier = calculate_block(h_n, verifier_block_key, result.key_encryptor.verifier_hash_value); - expected_verifier.resize(calculated_verifier.size()); - - if (calculated_verifier.size() != expected_verifier.size() - || std::mismatch(calculated_verifier.begin(), calculated_verifier.end(), expected_verifier.begin(), - expected_verifier.end()) - != std::make_pair(calculated_verifier.end(), expected_verifier.end())) - { - throw xlnt::exception("bad password"); - } - - const std::array key_value_block_key = { - {0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6}}; - auto key = calculate_block(h_n, key_value_block_key, result.key_encryptor.encrypted_key_value); - - auto salt_size = result.key_data.salt_size; - auto salt_with_block_key = result.key_data.salt_value; - salt_with_block_key.resize(salt_size + sizeof(std::uint32_t), 0); - - auto &segment = *reinterpret_cast(salt_with_block_key.data() + salt_size); - auto total_size = static_cast(*reinterpret_cast(encrypted_package.data())); - - std::vector encrypted_segment(segment_length, 0); - std::vector decrypted_package; - decrypted_package.reserve(encrypted_package.size() - 8); - - for (std::size_t i = 8; i < encrypted_package.size(); i += segment_length) - { - auto iv = hash(result.key_encryptor.hash, salt_with_block_key); - iv.resize(16); - - auto segment_begin = encrypted_package.begin() + static_cast(i); - auto current_segment_length = std::min(segment_length, encrypted_package.size() - i); - auto segment_end = encrypted_package.begin() + static_cast(i + current_segment_length); - encrypted_segment.assign(segment_begin, segment_end); - auto decrypted_segment = - aes(key, iv, encrypted_segment, cipher_chaining::cbc, cipher_direction::decryption); - decrypted_segment.resize(current_segment_length); - - decrypted_package.insert(decrypted_package.end(), decrypted_segment.begin(), decrypted_segment.end()); - - ++segment; - } - - decrypted_package.resize(total_size); - - return decrypted_package; + throw xlnt::exception("empty file"); } - static std::vector decrypt_xlsx(const std::vector &bytes, const std::string &password) + std::vector as_chars(bytes.begin(), bytes.end()); + POLE::Storage storage(as_chars.data(), static_cast(bytes.size())); + + if (!storage.open()) { - if (bytes.empty()) - { - throw xlnt::exception("empty file"); - } + throw xlnt::exception("not an ole compound file"); + } - std::vector as_chars(bytes.begin(), bytes.end()); - POLE::Storage storage(as_chars.data(), static_cast(bytes.size())); + auto encrypted_package = file(storage, "EncryptedPackage"); + auto encryption_info = file(storage, "EncryptionInfo"); - if (!storage.open()) - { - throw xlnt::exception("not an ole compound file"); - } + std::size_t index = 0; - auto encrypted_package = file(storage, "EncryptedPackage"); - auto encryption_info = file(storage, "EncryptionInfo"); + auto version_major = read_int(index, encryption_info); + auto version_minor = read_int(index, encryption_info); + auto encryption_flags = read_int(index, encryption_info); - std::size_t index = 0; + // get rid of header + encryption_info.erase(encryption_info.begin(), encryption_info.begin() + static_cast(index)); - auto version_major = read_int(index, encryption_info); - auto version_minor = read_int(index, encryption_info); - auto encryption_flags = read_int(index, encryption_info); - - // get rid of header - encryption_info.erase(encryption_info.begin(), encryption_info.begin() + static_cast(index)); - - // version 4.4 is agile - if (version_major == 4 && version_minor == 4) - { - if (encryption_flags != 0x40) - { - throw xlnt::exception("bad header"); - } - - return decrypt_xlsx_agile(encryption_info, password, encrypted_package); - } - - // not agile, only try to decrypt versions 3.2 and 4.2 - if (version_minor != 2 || (version_major != 2 && version_major != 3 && version_major != 4)) - { - throw xlnt::exception("unsupported encryption version"); - } - - if ((encryption_flags & 0b00000011) != 0) // Reserved1 and Reserved2, MUST be 0 + // version 4.4 is agile + if (version_major == 4 && version_minor == 4) + { + if (encryption_flags != 0x40) { throw xlnt::exception("bad header"); } - if ((encryption_flags & 0b00000100) == 0 // fCryptoAPI - || (encryption_flags & 0b00010000) != 0) // fExternal - { - throw xlnt::exception("extensible encryption is not supported"); - } - - if ((encryption_flags & 0b00100000) == 0) // fAES - { - throw xlnt::exception("not an OOXML document"); - } - - return decrypt_xlsx_standard(encryption_info, password, encrypted_package); + return decrypt_xlsx_agile(encryption_info, password, encrypted_package); } - static std::vector encrypt_xlsx(const std::vector &bytes, const std::string &password) + // not agile, only try to decrypt versions 3.2 and 4.2 + if (version_minor != 2 || (version_major != 2 && version_major != 3 && version_major != 4)) { - if (bytes.empty()) - { - throw xlnt::exception("empty file"); - } - - generate_agile_encryption_info(password); - - return {}; + throw xlnt::exception("unsupported encryption version"); } -}; + + if ((encryption_flags & 0b00000011) != 0) // Reserved1 and Reserved2, MUST be 0 + { + throw xlnt::exception("bad header"); + } + + if ((encryption_flags & 0b00000100) == 0 // fCryptoAPI + || (encryption_flags & 0b00010000) != 0) // fExternal + { + throw xlnt::exception("extensible encryption is not supported"); + } + + if ((encryption_flags & 0b00100000) == 0) // fAES + { + throw xlnt::exception("not an OOXML document"); + } + + return decrypt_xlsx_standard(encryption_info, password, encrypted_package); +} + +std::vector crypto_helper::encrypt_xlsx( + const std::vector &bytes, const std::string &password) +{ + if (bytes.empty()) + { + throw xlnt::exception("empty file"); + } + + generate_agile_encryption_info(password); + + return {}; +} const std::size_t crypto_helper::segment_length = 4096; diff --git a/source/detail/xlsx_crypto.hpp b/source/detail/xlsx_crypto.hpp new file mode 100644 index 00000000..1362f5d8 --- /dev/null +++ b/source/detail/xlsx_crypto.hpp @@ -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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 aes(const std::vector &key, const std::vector &iv, + const std::vector &source, cipher_chaining chaining, cipher_direction direction); + + static std::vector decode_base64(const std::string &encoded); + + static std::string encode_base64(const std::vector &decoded); + + static std::vector hash(hash_algorithm algorithm, const std::vector &input); + + static std::vector 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 salt_value; + std::vector verifier_hash_input; + std::vector verifier_hash_value; + std::vector encrypted_key_value; + }; + + static std::vector decrypt_xlsx_standard(const std::vector &encryption_info, + const std::string &password, const std::vector &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 salt_value; + } key_data; + + struct + { + std::vector hmac_key; + std::vector 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 salt_value; + std::vector verifier_hash_input; + std::vector verifier_hash_value; + std::vector encrypted_key_value; + } key_encryptor; + }; + + static agile_encryption_info generate_agile_encryption_info(const std::string &password); + + static std::vector write_agile_encryption_info(const std::string &password); + + static std::vector decrypt_xlsx_agile(const std::vector &encryption_info, + const std::string &password, const std::vector &encrypted_package); + + static std::vector decrypt_xlsx(const std::vector &bytes, const std::string &password); + + static std::vector encrypt_xlsx(const std::vector &bytes, const std::string &password); +}; + +} // namespace detail +} // namespace xlnt diff --git a/source/detail/xlsx_producer.cpp b/source/detail/xlsx_producer.cpp index f93466b3..da8edac5 100644 --- a/source/detail/xlsx_producer.cpp +++ b/source/detail/xlsx_producer.cpp @@ -218,7 +218,7 @@ void xlsx_producer::write_property(const std::string &name, const variant &value write_start_element(constants::ns("vt"), "bool"); } - write_characters(write_bool(value.get())); + write_characters(value.get() ? "true" : "false"); if (custom) { @@ -336,6 +336,7 @@ void xlsx_producer::write_core_properties(const relationship &/*rel*/) for (const auto &ns : core_property_namespace(prop)) { if (namespaces.count(ns.first) > 0) continue; + write_namespace(ns.first, ns.second); namespaces.emplace(ns); } diff --git a/source/detail/zstream.cpp b/source/detail/zstream.cpp index 9032e70e..948d8297 100644 --- a/source/detail/zstream.cpp +++ b/source/detail/zstream.cpp @@ -432,7 +432,7 @@ protected: 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); @@ -494,7 +494,7 @@ izstream::izstream(std::istream &stream) { if (!stream) { - throw std::runtime_error("ZIP: Invalid file handle"); + throw xlnt::exception("Invalid file handle"); } read_central_header(); @@ -522,15 +522,20 @@ bool izstream::read_central_header() } source_stream_.seekg(end_position - read_start); - std::vector buf(static_cast(read_start), '\0'); + std::vector buf(static_cast(read_start), '\0'); if (read_start <= 0) { - std::cerr << "ZIP: Invalid read buffer size" << std::endl; - return false; + throw xlnt::exception("file is empty"); } - source_stream_.read(buf.data(), read_start); + source_stream_.read(reinterpret_cast(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; std::size_t header_index = 0; @@ -547,8 +552,7 @@ bool izstream::read_central_header() if (!found_header) { - std::cerr << "ZIP: Failed to find zip header" << std::endl; - return false; + throw xlnt::exception("failed to find zip header"); } // 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) { - std::cerr << "ZIP: multiple disk zip files are not supported" << std::endl; - return false; + throw xlnt::exception("multiple disk zip files are not supported"); } auto num_files = read_int(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) { - std::cerr << "ZIP: multi disk zip files are not supported" << std::endl; - return false; + throw xlnt::exception("multi disk zip files are not supported"); } /*auto size_of_header = */ read_int(source_stream_); // size of header @@ -592,7 +594,7 @@ std::unique_ptr izstream::open(const path &filename) const { if (!has_file(filename)) { - throw "not found"; + throw xlnt::exception("file not found"); } auto header = file_headers_.at(filename.string()); diff --git a/source/workbook/tests/test_produce_xlsx.hpp b/source/workbook/tests/test_produce_xlsx.hpp index b2373a93..047ce6b8 100644 --- a/source/workbook/tests/test_produce_xlsx.hpp +++ b/source/workbook/tests/test_produce_xlsx.hpp @@ -134,7 +134,7 @@ public: TS_ASSERT(!temp_buffer.empty()); } - void test_write_comments() + void test_write_comments_hyperlinks_formulae() { xlnt::workbook wb; auto sheet1 = wb.active_sheet(); @@ -146,6 +146,15 @@ public: sheet1.cell("A2").value("Sheet1!A2"); 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(); sheet2.cell("A1").value("Sheet2!A1"); 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").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"); TS_ASSERT(workbook_matches_file(wb, path)); } diff --git a/source/workbook/tests/test_round_trip.hpp b/source/workbook/tests/test_round_trip.hpp index cb6948a2..5ad23d2c 100644 --- a/source/workbook/tests/test_round_trip.hpp +++ b/source/workbook/tests/test_round_trip.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -16,27 +17,34 @@ public: /// 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. /// - 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); - std::vector original_data; + xlnt::workbook source_workbook; + source_workbook.load(source); - { - xlnt::detail::vector_ostreambuf file_data_buffer(original_data); - std::ostream file_data_stream(&file_data_buffer); - file_data_stream << file_stream.rdbuf(); - } + std::vector destination; + source_workbook.save(destination); - xlnt::workbook original_workbook; - original_workbook.load(original); - - std::vector buffer; - original_workbook.save(buffer); - original_workbook.save("round_trip_out.xlsx"); + std::ifstream source_stream(source.string(), std::ios::binary); - return xml_helper::xlsx_archives_match(original_data, buffer); + return xml_helper::xlsx_archives_match(xlnt::detail::to_vector(source_stream), destination); } + bool round_trip_matches_rw(const xlnt::path &source, const std::string &password) + { + xlnt::workbook source_workbook; + source_workbook.load(source, password); + + std::vector destination; + source_workbook.save(destination); + + 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() { const auto files = std::vector @@ -44,10 +52,6 @@ public: "2_minimal", "3_default", "4_every_style", - "5_encrypted_agile", - "6_encrypted_libre", - "7_encrypted_standard", - "8_encrypted_numbers", "10_comments_hyperlinks_formulae", "11_print_settings", "12_advanced_properties" @@ -59,4 +63,22 @@ public: TS_ASSERT(round_trip_matches_rw(path)); } } + + void test_round_trip_empty_excel_rw_encrypted() + { + const auto files = std::vector + { + "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")); + } + } }; diff --git a/source/workbook/workbook.cpp b/source/workbook/workbook.cpp index 56f7bbc7..ed14078a 100644 --- a/source/workbook/workbook.cpp +++ b/source/workbook/workbook.cpp @@ -107,9 +107,9 @@ void open_stream(std::ofstream &stream, const std::string &path) #endif template -std::vector keys(const T &container) +std::vector keys(const std::vector> &container) { - auto result = std::vector(); + auto result = std::vector(); auto iter = container.begin(); while (iter != container.end()) @@ -120,6 +120,20 @@ std::vector keys(const T &container) return result; } +template +bool contains(const std::vector> &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) { using xlnt::path; @@ -285,7 +299,7 @@ namespace xlnt { 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 workbook::core_properties() const @@ -295,18 +309,36 @@ std::vector workbook::core_properties() 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) { 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 { - return d_->extended_properties_.count(type) > 0; + return ::contains(d_->extended_properties_, type); } std::vector workbook::extended_properties() const @@ -317,17 +349,35 @@ std::vector workbook::extended_properties() const void workbook::extended_property(xlnt::extended_property type, const variant &value) { 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 { - 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 { - return d_->custom_properties_.count(property_name) > 0; + return ::contains(d_->custom_properties_, property_name); } std::vector workbook::custom_properties() const @@ -338,12 +388,30 @@ std::vector workbook::custom_properties() const void workbook::custom_property(const std::string &property_name, const variant &value) { 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 { - 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() @@ -367,7 +435,9 @@ workbook workbook::empty() 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::scale_crop, "false"); + wb.extended_property(xlnt::extended_property::scale_crop, false); + wb.extended_property(xlnt::extended_property::heading_pairs, std::vector{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::links_up_to_date, false); wb.extended_property(xlnt::extended_property::shared_doc, false); @@ -793,25 +863,6 @@ void workbook::load(const std::vector &data) 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)); } @@ -830,25 +881,6 @@ void workbook::load(const path &filename) 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); } @@ -891,9 +923,21 @@ void workbook::save(std::vector &data) const save(data_stream); } +void workbook::save(std::vector &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 { - 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 @@ -903,27 +947,34 @@ void workbook::save(const path &filename) const 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 { detail::xlsx_producer producer(*this); 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); producer.write(stream, password); } #ifdef _MSC_VER -void workbook::save(const std::wstring &filename) +void workbook::save(const std::wstring &filename) const { std::ofstream file_stream; open_stream(file_stream, filename); 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; open_stream(file_stream, filename); @@ -1402,33 +1453,21 @@ std::size_t workbook::rup_build() const return d_->file_version_.get().rup_build; } -/// -/// -/// bool workbook::has_calculation_properties() const { return d_->calculation_properties_.is_set(); } -/// -/// -/// class calculation_properties workbook::calculation_properties() const { return d_->calculation_properties_.get(); } -/// -/// -/// void workbook::calculation_properties(const class calculation_properties &props) { d_->calculation_properties_ = props; } -/// -/// Removes calcChain part from manifest if no formulae remain in workbook. -/// void workbook::garbage_collect_formulae() { auto any_with_formula = false; diff --git a/source/worksheet/tests/test_worksheet.hpp b/source/worksheet/tests/test_worksheet.hpp index 15c95dd3..e52e242b 100644 --- a/source/worksheet/tests/test_worksheet.hpp +++ b/source/worksheet/tests/test_worksheet.hpp @@ -222,7 +222,8 @@ public: xlnt::workbook wb; auto ws = wb.active_sheet(); ws.cell("A1").hyperlink("http://test.com"); - TS_ASSERT_EQUALS("http://test.com", ws.cell("A1").value()); + TS_ASSERT_EQUALS(ws.cell("A1").hyperlink(), "http://test.com"); + TS_ASSERT_EQUALS(ws.cell("A1").value(), ""); ws.cell("A1").value("test"); TS_ASSERT_EQUALS("test", ws.cell("A1").value()); TS_ASSERT_EQUALS(ws.cell("A1").hyperlink(), "http://test.com"); diff --git a/source/worksheet/worksheet.cpp b/source/worksheet/worksheet.cpp index 6c450447..445de53a 100644 --- a/source/worksheet/worksheet.cpp +++ b/source/worksheet/worksheet.cpp @@ -1010,6 +1010,11 @@ void worksheet::register_comments_in_manifest() 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 { return d_->header_footer_.is_set(); diff --git a/tests/helpers/xml_helper.hpp b/tests/helpers/xml_helper.hpp index 3b4d6e59..aa03e8f2 100644 --- a/tests/helpers/xml_helper.hpp +++ b/tests/helpers/xml_helper.hpp @@ -16,6 +16,8 @@ public: { // content types are stored in unordered maps, too complicated to compare 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/" && content_type.substr(content_type.size() - 4) == "+xml") @@ -200,7 +202,7 @@ public: std::cout << "right is missing file: " << left_member.string() << std::endl; break; } - + auto left_content_type = left_member.string() == "[Content_Types].xml" ? "[Content_Types].xml" : left_manifest.content_type(left_member); auto right_content_type = left_member.string() == "[Content_Types].xml"