From 33abb7b5b11d2eaa19ce021a962a8e5e8ef53d9f Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sun, 1 Oct 2023 16:15:47 -0500 Subject: [PATCH] App/Toponaming: Add base64 encoding to XML --- src/Base/Base64.cpp | 232 +++++++++++--------- src/Base/Base64.h | 441 ++++++++++++++++++++++++++++++++++++-- src/Base/FileInfo.h | 9 + src/Base/Reader.cpp | 16 +- src/Base/Reader.h | 6 +- src/Base/Writer.cpp | 24 ++- src/Base/Writer.h | 9 +- tests/src/Base/Reader.cpp | 18 ++ tests/src/Base/Writer.cpp | 15 ++ 9 files changed, 634 insertions(+), 136 deletions(-) diff --git a/src/Base/Base64.cpp b/src/Base/Base64.cpp index eb761e1eea..ab45e70e30 100644 --- a/src/Base/Base64.cpp +++ b/src/Base/Base64.cpp @@ -1,128 +1,162 @@ -/* - base64.cpp and base64.h +/* +base64.cpp and base64.h - Copyright (C) 2004-2008 René Nyffenegger +Copyright (C) 2004-2008 René Nyffenegger - This source code is provided 'as-is', without any express or implied - warranty. In no event will the author be held liable for any damages - arising from the use of this software. +This source code is provided 'as-is', without any express or implied +warranty. In no event will the author be held liable for any damages +arising from the use of this software. - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: - 1. The origin of this source code must not be misrepresented; you must not - claim that you wrote the original source code. If you use this source code - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. +1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original source code. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. - 3. This notice may not be removed or altered from any source distribution. +3. This notice may not be removed or altered from any source distribution. - René Nyffenegger rene.nyffenegger@adp-gmbh.ch +René Nyffenegger rene.nyffenegger@adp-gmbh.ch + + +Copyright (C) 2019 Zheng Lei (realthunder.dev@gmail.com) + +* Added support of in-line transformation using boost iostream for memory + efficiency */ - #include "PreCompiled.h" #ifndef _PreComp_ -# include +#include +#include #endif #include "Base64.h" -// clazy:excludeall=non-pod-global-static -static const std::string base64_chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; + +static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; -static inline bool is_base64(unsigned char c) { - return (isalnum(c) || (c == '+') || (c == '/')); +const signed char* Base::base64_decode_table() +{ + static const signed char _table[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -2, -1, -2, -1, -1, // 0-15 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 16-31 + -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, // 32-47 + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, // 48-63 + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 64-79 + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, // 80-95 + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 96-111 + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, // 112-127 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 128-143 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 144-159 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 160-175 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 176-191 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 192-207 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 208-223 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 224-239 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 // 240-255 + }; + return _table; } -std::string Base::base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { - std::string ret; - int i = 0; - int j = 0; - unsigned char char_array_3[3]; - unsigned char char_array_4[4]; +std::size_t Base::base64_encode(char* out, void const* in, std::size_t in_len) +{ + char* ret = out; + unsigned char const* bytes_to_encode = reinterpret_cast(in); + int i = 0; + int j = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; - while (in_len--) { - char_array_3[i++] = *(bytes_to_encode++); - if (i == 3) { - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - char_array_4[3] = char_array_3[2] & 0x3f; + while (in_len--) { + char_array_3[i++] = *(bytes_to_encode++); + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; - for(i = 0; (i <4) ; i++) - ret += base64_chars[char_array_4[i]]; - i = 0; + for (i = 0; (i < 4); i++) { + *ret++ = base64_chars[char_array_4[i]]; + } + i = 0; + } } - } - if (i) - { - for(j = i; j < 3; j++) - char_array_3[j] = '\0'; + if (i) { + for (j = i; j < 3; j++) { + char_array_3[j] = '\0'; + } - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - char_array_4[3] = char_array_3[2] & 0x3f; + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; - for (j = 0; (j < i + 1); j++) - ret += base64_chars[char_array_4[j]]; + for (j = 0; (j < i + 1); j++) { + *ret++ = base64_chars[char_array_4[j]]; + } - while((i++ < 3)) - ret += '='; - - } - - return ret; - -} - -std::string Base::base64_decode(std::string const& encoded_string) { - int in_len = encoded_string.size(); - int i = 0; - int j = 0; - int in_ = 0; - unsigned char char_array_4[4], char_array_3[3]; - std::string ret; - - while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { - char_array_4[i++] = encoded_string[in_]; in_++; - if (i ==4) { - for (i = 0; i <4; i++) - char_array_4[i] = base64_chars.find(char_array_4[i]); - - char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); - char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - - for (i = 0; (i < 3); i++) - ret += char_array_3[i]; - i = 0; + while ((i++ < 3)) { + *ret++ = '='; + } } - } - if (i) { - for (j = i; j <4; j++) - char_array_4[j] = 0; - - for (j = 0; j <4; j++) - char_array_4[j] = base64_chars.find(char_array_4[j]); - - char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); - char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - - for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; - } - - return ret; + return ret - out; +} + +std::pair +Base::base64_decode(void* _out, char const* in, std::size_t in_len) +{ + unsigned char* out = reinterpret_cast(_out); + unsigned char* ret = out; + char const* input = in; + int i = 0; + int j = 0; + unsigned char char_array_4[4], char_array_3[3]; + + static const signed char* table = base64_decode_table(); + + while (in_len-- && *in != '=') { + const signed char v = table[(int)(*in++)]; + if (v < 0) { + break; + } + + char_array_4[i++] = (unsigned char)v; + if (i == 4) { + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (i = 0; (i < 3); i++) { + *ret++ = char_array_3[i]; + } + i = 0; + } + } + + if (i) { + for (j = i; j < 4; j++) { + char_array_4[j] = 0; + } + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (j = 0; (j < i - 1); j++) { + *ret++ = char_array_3[j]; + } + } + + return std::make_pair((std::size_t)(ret - out), (std::size_t)(in - input)); } diff --git a/src/Base/Base64.h b/src/Base/Base64.h index d5430a0acb..30a0ec604b 100644 --- a/src/Base/Base64.h +++ b/src/Base/Base64.h @@ -1,42 +1,439 @@ -/* - base64.cpp and base64.h +/* +base64.cpp and base64.h - Copyright (C) 2004-2008 René Nyffenegger +Copyright (C) 2004-2008 René Nyffenegger - This source code is provided 'as-is', without any express or implied - warranty. In no event will the author be held liable for any damages - arising from the use of this software. +This source code is provided 'as-is', without any express or implied +warranty. In no event will the author be held liable for any damages +arising from the use of this software. - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: - 1. The origin of this source code must not be misrepresented; you must not - claim that you wrote the original source code. If you use this source code - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. +1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original source code. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. - 3. This notice may not be removed or altered from any source distribution. +3. This notice may not be removed or altered from any source distribution. - René Nyffenegger rene.nyffenegger@adp-gmbh.ch +René Nyffenegger rene.nyffenegger@adp-gmbh.ch +Copyright (C) 2019 Zheng Lei (realthunder.dev@gmail.com) + +* Added support of in-line transformation using boost iostream for memory + efficiency */ #ifndef BASE_BASE64_H #define BASE_BASE64_H -#include +#include +#include +#include +#include +#include #include - +#include namespace Base { - std::string BaseExport base64_encode(unsigned char const* , unsigned int len); - std::string BaseExport base64_decode(std::string const& s); +enum class Base64ErrorHandling +{ + throws, + silent +}; +/// Returns the max bytes of a encoded base64 string +inline std::size_t base64_encode_size(std::size_t len) +{ + return 4 * ((len + 2) / 3); } -#endif +/// Returns the max bytes of a decoded base64 binary string +inline std::size_t base64_decode_size(std::size_t len) +{ + return len / 4 * 3; +} + +/** Encode input binary with base64 + * @param out: output buffer with minimum size of base64_encode(len) + * appending new data. + * @param in: input binary data + * @param len: input length + * @return The character count written to output. + */ +BaseExport std::size_t base64_encode(char* out, void const* in, std::size_t len); + +/** Return the internal base64 decoding table + * + * The table maps from any 8-bit character to the decoded binary bits. + * Valid base64 characters are mapped to the corresponding 6-bit binary + * data. White space (space, tab, vtab, CR and LF) characters are mapped + * to -2. Other invalid characters are mapped to -1. + */ +BaseExport const signed char* base64_decode_table(); + +/** Decode the input base64 string into binary data + * @param out: output buffer with minimum size of base64_encode(len) + * appending new data. + * @param in: input binary data + * @param len: input length + * @return Return a pair of output size and input read size. Compare the + * read size to input size to check for error. + */ +BaseExport std::pair +base64_decode(void* out, char const*, std::size_t len); + +/** Encode input binary into base64 string + * @param out: output string. Note that the string is not cleared before + * adding new content. + * @param in: input binary data + * @param len: input length + */ +inline void base64_encode(std::string& out, void const* in, std::size_t len) +{ + std::size_t size = out.size(); + out.resize(size + base64_encode_size(len)); + len = base64_encode(&out[size], in, len); + out.resize(size + len); +} + +/** Encode input binary into base64 string + * @param in: input binary data + * @param len: input length + * @return Return the base64 string. + */ +inline std::string base64_encode(void const* in, std::size_t len) +{ + std::string out; + base64_encode(out, in, len); + return out; +} + +/** Decode base64 string into binary data + * @param out: output binary data. Note that the data is not cleared before + * adding new content. + * @param in: input base64 string + * @param len: input length + * @return Return the processed input length. Compare this with the + * argument \c len to check for error. + */ +template +inline std::size_t base64_decode(T& out, char const* in, std::size_t len) +{ + std::size_t size = out.size(); + out.resize(size + base64_decode_size(len)); + std::pair res = base64_decode(&out[size], in, len); + out.resize(size + res.first); + return res.second; +} + +/** Decode base64 string into binary data + * @param out: output binary data. Note that the data is not cleared before + * adding new content. + * @param s: input base64 string + * @return Return the processed input length. Compare this with the + * argument \c len to check for error. + */ +template +inline std::size_t base64_decode(T& out, std::string const& s) +{ + return base64_decode(out, s.c_str(), s.size()); +} + +/** Decode base64 string into binary data + * @param out adding new content. + * @param s: input base64 string + * @return Return the decoded binary data. + */ +inline std::string base64_decode(std::string const& s) +{ + std::string out; + base64_decode(out, s.c_str(), s.size()); + return out; +} + +namespace bio = boost::iostreams; + +/** A base64 encoder that can be used as a boost iostream filter + * + * @sa See create_base64_encoder() for example usage + */ +struct base64_encoder +{ + + typedef char char_type; + struct category: bio::multichar_output_filter_tag, + bio::closable_tag, + bio::optimally_buffered_tag + { + }; + + /** Constructor + * @param line_size: line size for the output base64 string, 0 to + * disable segmentation. + */ + base64_encoder(std::size_t line_size) + : line_size(line_size) + {} + + std::streamsize optimal_buffer_size() const + { + return base64_decode_size(line_size); + } + + template + void close(Device& dev) + { + if (pending_size) { + base64_encode(buffer, pending, pending_size); + } + if (buffer.size()) { + bio::write(dev, buffer.c_str(), buffer.size()); + if (line_size) { + bio::put(dev, '\n'); + } + buffer.clear(); + } + else if (pos && line_size) { + bio::put(dev, '\n'); + } + } + + template + std::streamsize write(Device& dev, const char_type* s, std::streamsize n) + { + std::streamsize res = n; + + if (pending_size) { + while (n && pending_size < 3) { + pending[pending_size++] = *s++; + --n; + } + if (pending_size != 3) { + return res; + } + + base64_encode(buffer, pending, 3); + } + pending_size = n % 3; + n = n / 3 * 3; + base64_encode(buffer, s, n); + s += n; + for (unsigned i = 0; i < pending_size; ++i) { + pending[i] = s[i]; + } + + const char* buf = buffer.c_str(); + const char* end = buf + buffer.size(); + if (line_size && buffer.size() >= line_size - pos) { + bio::write(dev, buf, line_size - pos); + bio::put(dev, '\n'); + buf += line_size - pos; + pos = 0; + for (; end - buf >= (int)line_size; buf += line_size) { + bio::write(dev, buf, line_size); + bio::put(dev, '\n'); + } + } + pos += end - buf; + bio::write(dev, buf, end - buf); + buffer.clear(); + return n; + } + + std::size_t line_size; + std::size_t pos = 0; + std::size_t pending_size = 0; + unsigned char pending[3]; + std::string buffer; +}; + +/** A base64 decoder that can be used as a boost iostream filter + * + * @sa See create_base64_decoder() for example usage + */ +struct base64_decoder +{ + + typedef char char_type; + struct category: bio::multichar_input_filter_tag, bio::optimally_buffered_tag + { + }; + + /** Constructor + * @param line_size: line size of the encoded base64 string. This is + * used just as a suggestion for better buffering. + * @param silent: whether to throw on invalid non white space character. + */ + base64_decoder(std::size_t line_size, Base64ErrorHandling errHandling) + : line_size(line_size) + , errHandling(errHandling) + {} + + std::streamsize optimal_buffer_size() const + { + return base64_encode_size(line_size != 0U ? line_size : 1024); + } + + template + std::streamsize read(Device& dev, char_type* s, std::streamsize n) + { + static const signed char* table = base64_decode_table(); + + if (!n) { + return 0; + } + + std::streamsize count = 0; + + for (;;) { + while (pending_out < out_count) { + *s++ = char_array_3[pending_out++]; + ++count; + if (--n == 0) { + return count; + } + } + + if (eof) { + return count ? count : -1; + } + + for (;;) { + int d = bio::get(dev); + if (d < 0) { + eof = true; + if (pending_in <= 1) { + if (pending_in == 1 && errHandling == Base64ErrorHandling::throws) { + throw BOOST_IOSTREAMS_FAILURE("Unexpected ending of base64 string"); + } + return count ? count : -1; + } + out_count = pending_in - 1; + pending_in = 4; + } + else { + signed char c = table[d]; + if (c < 0) { + if (c == -2 || errHandling == Base64ErrorHandling::silent) { + continue; + } + throw BOOST_IOSTREAMS_FAILURE("Invalid character in base64 string"); + } + char_array_4[pending_in++] = (char)c; + } + if (pending_in == 4) { + pending_out = pending_in = 0; + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = + ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + break; + } + } + } + } + + std::size_t line_size; + std::uint8_t pending_in = 0; + char char_array_4[4]; + std::uint8_t pending_out = 3; + std::uint8_t out_count = 3; + char char_array_3[3]; + Base64ErrorHandling errHandling; + bool eof = false; +}; + +/** Create an output stream that transforms the input binary data to base64 strings + * + * @param out: the downstream output stream that will be fed with base64 string + * @param line_size: line size of the base64 string. Zero to disable segmenting. + * + * @return A unique pointer to an output stream that can transforms the + * input binary data to base64 strings. + */ +inline std::unique_ptr create_base64_encoder(std::ostream& out, + std::size_t line_size = 80) +{ + std::unique_ptr res(new bio::filtering_ostream); + auto* f = static_cast(res.get()); + f->push(base64_encoder(line_size)); + f->push(out); + return res; +} + +/** Create an output stream that stores the input binary data to file as base64 strings + * + * @param filename: the output file path + * @param line_size: line size of the base64 string. Zero to disable segmenting. + * + * @return A unique pointer to an output stream that can transforms the + * input binary data to base64 strings. + */ +inline std::unique_ptr create_base64_encoder(const std::string& filepath, + std::size_t line_size = 80) +{ + std::unique_ptr res(new bio::filtering_ostream); + bio::filtering_ostream* f = static_cast(res.get()); + f->push(base64_encoder(line_size)); + f->push(bio::file_sink(filepath)); + return res; +} + +/** Create an input stream that can transform base64 into binary + * + * @param in: input upstream. + * @param line_size: line size of the encoded base64 string. This is + * used just as a suggestion for better buffering. + * @param silent: whether to throw on invalid non white space character. + * + * @return A unique pointer to an input stream that read from the given + * upstream and transform the read base64 strings into binary data. + */ +inline std::unique_ptr +create_base64_decoder(std::istream& in, + std::size_t line_size = 80, + Base64ErrorHandling errHandling = Base64ErrorHandling::silent) +{ + std::unique_ptr res(new bio::filtering_istream); + bio::filtering_istream* f = static_cast(res.get()); + f->push(base64_decoder(line_size, errHandling)); + f->push(in); + return res; +} + +/** Create an input stream that can transform base64 into binary + * + * @param filepath: input file. + * @param ending: optional ending character. If non zero, the filter + * will signal EOF when encounter this character. + * @param putback: if true and the filter read the ending character + * it will put it back into upstream + * @param line_size: line size of the encoded base64 string. This is + * used just as a suggestion for better buffering. + * @param silent: whether to throw on invalid non white space character. + * + * @return A unique pointer to an input stream that read from the given + * file and transform the read base64 strings into binary data. + */ +inline std::unique_ptr +create_base64_decoder(const std::string& filepath, + std::size_t line_size = 80, + Base64ErrorHandling errHandling = Base64ErrorHandling::silent) +{ + std::unique_ptr res(new bio::filtering_istream); + auto* f = static_cast(res.get()); + f->push(base64_decoder(line_size, errHandling)); + f->push(bio::file_source(filepath)); + return res; +} + +} // namespace Base + +#endif diff --git a/src/Base/FileInfo.h b/src/Base/FileInfo.h index 52e0682b39..e8597232b5 100644 --- a/src/Base/FileInfo.h +++ b/src/Base/FileInfo.h @@ -34,6 +34,15 @@ namespace Base { +/// When reading and writing a character stream, the incoming data can be dumped into the stream +/// unaltered (if it contains only data that is valid in the current XML character set), or it can +/// be Base64-encoded. This enum is used by Reader and Writer to distinguish the two cases. +enum class CharStreamFormat { + Raw, + Base64Encoded +}; +static constexpr int base64LineWidth {80}; + /** File name unification * This class handles everything related to file names * the file names are internal generally UTF-8 encoded on diff --git a/src/Base/Reader.cpp b/src/Base/Reader.cpp index a2177feeac..83caec65fe 100644 --- a/src/Base/Reader.cpp +++ b/src/Base/Reader.cpp @@ -283,8 +283,17 @@ void Base::XMLReader::readEndElement(const char* ElementName, int level) || (level>=0 && level!=Level)))); } -void Base::XMLReader::readCharacters() +void Base::XMLReader::readCharacters(const char* filename, CharStreamFormat format) { + Base::FileInfo fi(filename); + Base::ofstream to(fi, std::ios::out | std::ios::binary | std::ios::trunc); + if (!to) { + throw Base::FileException("XMLReader::readCharacters() Could not open file!"); + } + + beginCharStream(format) >> to.rdbuf(); + to.close(); + endCharStream(); } std::streamsize Base::XMLReader::read(char_type* s, std::streamsize n) @@ -336,7 +345,7 @@ std::istream& Base::XMLReader::charStream() return *CharStream; } -std::istream& Base::XMLReader::beginCharStream() +std::istream& Base::XMLReader::beginCharStream(CharStreamFormat format) { if (CharStream) { throw Base::XMLParseException("recursive character stream"); @@ -362,6 +371,9 @@ std::istream& Base::XMLReader::beginCharStream() CharStream = std::make_unique(); auto* filteringStream = dynamic_cast(CharStream.get()); + if(format == CharStreamFormat::Base64Encoded) { + filteringStream->push(base64_decoder(Base::base64LineWidth, Base64ErrorHandling::silent)); + } filteringStream->push(boost::ref(*this)); return *CharStream; } diff --git a/src/Base/Reader.h b/src/Base/Reader.h index 9f3831548d..8ec9d466e8 100644 --- a/src/Base/Reader.h +++ b/src/Base/Reader.h @@ -175,7 +175,7 @@ public: */ void readEndElement(const char* ElementName=nullptr, int level=-1); /// read until characters are found - void readCharacters(); + void readCharacters(const char* filename, CharStreamFormat format = CharStreamFormat::Raw); /** Obtain an input stream for reading characters * @@ -183,7 +183,7 @@ public: * auto destroyed when you call with readElement() or readEndElement(), or * you can end it explicitly with endCharStream(). */ - std::istream &beginCharStream(); + std::istream &beginCharStream(CharStreamFormat format = CharStreamFormat::Raw); /// Manually end the current character stream void endCharStream(); /// Obtain the current character stream @@ -200,7 +200,7 @@ public: unsigned int getAttributeCount() const; /// check if the read element has a special attribute bool hasAttribute(const char* AttrName) const; - /// return the named attribute as an interer (does type checking) + /// return the named attribute as an integer (does type checking) long getAttributeAsInteger(const char* AttrName) const; unsigned long getAttributeAsUnsigned(const char* AttrName) const; /// return the named attribute as a double floating point (does type checking) diff --git a/src/Base/Writer.cpp b/src/Base/Writer.cpp index b91701697a..4ecc1ad370 100644 --- a/src/Base/Writer.cpp +++ b/src/Base/Writer.cpp @@ -83,18 +83,22 @@ Writer::Writer() Writer::~Writer() = default; -std::ostream& Writer::beginCharStream() +std::ostream& Writer::beginCharStream(CharStreamFormat format) { if (CharStream) { throw Base::RuntimeError("Writer::beginCharStream(): invalid state"); } - - Stream() << "(); - auto* filteredStream = dynamic_cast(CharStream.get()); - filteredStream->push(cdata_filter()); - filteredStream->push(Stream()); - *filteredStream << std::setprecision(std::numeric_limits::digits10 + 1); + charStreamFormat = format; + if(format == CharStreamFormat::Base64Encoded) { + CharStream = create_base64_encoder(Stream(), Base::base64LineWidth); + } else { + Stream() << "(); + auto* filteredStream = dynamic_cast(CharStream.get()); + filteredStream->push(cdata_filter()); + filteredStream->push(Stream()); + *filteredStream << std::setprecision(std::numeric_limits::digits10 + 1); + } return *CharStream; } @@ -102,7 +106,9 @@ std::ostream& Writer::endCharStream() { if (CharStream) { CharStream.reset(); - Stream() << "]]>"; + if (charStreamFormat == CharStreamFormat::Raw) { + Stream() << "]]>"; + } } return Stream(); } diff --git a/src/Base/Writer.h b/src/Base/Writer.h index 51cf93cee6..8f2d13465d 100644 --- a/src/Base/Writer.h +++ b/src/Base/Writer.h @@ -123,11 +123,17 @@ public: * the current XML encoding, and will be enclosed inside * CDATA section. The stream will scan the input and * properly escape any CDATA ending inside. + * + * @param format: If Base64Encoded, the input will be base64 encoded before storing. + * If Raw, the input is assumed to be valid character with + * the current XML encoding, and will be enclosed inside + * CDATA section. The stream will scan the input and + * properly escape any CDATA ending inside. * @return Returns an output stream. * * You must call endCharStream() to end the current character stream. */ - std::ostream &beginCharStream(); + std::ostream& beginCharStream(CharStreamFormat format = CharStreamFormat::Raw); /** End the current character output stream * @return Returns the normal writer stream for convenience */ @@ -161,6 +167,7 @@ public: private: std::unique_ptr CharStream; + CharStreamFormat charStreamFormat; }; diff --git a/tests/src/Base/Reader.cpp b/tests/src/Base/Reader.cpp index 2ddf59dc48..25fee6bbc1 100644 --- a/tests/src/Base/Reader.cpp +++ b/tests/src/Base/Reader.cpp @@ -265,3 +265,21 @@ TEST_F(ReaderTest, readNextStartEndElement) EXPECT_STREQ(Reader()->localName(), "node2"); EXPECT_STREQ(Reader()->getAttribute("attr"), "2"); } + +TEST_F(ReaderTest, charStreamBase64Encoded) +{ + // Arrange + static constexpr size_t bufferSize {100}; + std::array buffer {}; + givenDataAsXMLStream("RnJlZUNBRCByb2NrcyEg8J+qqPCfqqjwn6qo\n"); + Reader()->readElement("data"); + Reader()->beginCharStream(Base::CharStreamFormat::Base64Encoded); + + // Act + Reader()->charStream().getline(buffer.data(), bufferSize); + Reader()->endCharStream(); + + // Assert + // Conversion done using https://www.base64encode.org for testing purposes + EXPECT_EQ(std::string("FreeCAD rocks! 🪨🪨🪨"), std::string(buffer.data())); +} diff --git a/tests/src/Base/Writer.cpp b/tests/src/Base/Writer.cpp index e5cf71f955..139a218c9c 100644 --- a/tests/src/Base/Writer.cpp +++ b/tests/src/Base/Writer.cpp @@ -114,3 +114,18 @@ TEST_F(WriterTest, charStream) // Assert EXPECT_EQ(&streamA, &streamB); } + +TEST_F(WriterTest, charStreamBase64Encoded) +{ + // Arrange + auto& stream {_writer.beginCharStream(Base::CharStreamFormat::Base64Encoded)}; + std::string data {"FreeCAD rocks! 🪨🪨🪨"}; + + // Act + _writer.charStream() << data; + _writer.endCharStream(); + + // Assert + // Conversion done using https://www.base64encode.org for testing purposes + EXPECT_EQ(std::string("RnJlZUNBRCByb2NrcyEg8J+qqPCfqqjwn6qo\n"), _writer.getString()); +}