// SPDX-License-Identifier: LGPL-2.1-or-later
/****************************************************************************
* *
* Copyright (c) 2019 Zheng Lei (realthunder.dev@gmail.com) *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* FreeCAD is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with FreeCAD. If not, see *
* . *
* *
***************************************************************************/
#ifndef FREECAD_BASE_BASE64FILTER_H
#define FREECAD_BASE_BASE64FILTER_H
#include "Base64.h"
#include "FCGlobal.h"
#include
#include
#include
#include
// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic,
// cppcoreguidelines-pro-bounds-constant-array-index, cppcoreguidelines-avoid-magic-numbers,
// readability-magic-numbers)
namespace Base
{
namespace bio = boost::iostreams;
enum class Base64ErrorHandling
{
throws,
silent
};
static constexpr int base64DefaultBufferSize {80};
/** A base64 encoder that can be used as a boost iostream filter
*
* @sa See create_base64_encoder() for example usage
*/
struct base64_encoder
{
using char_type = char;
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.
*/
explicit base64_encoder(std::size_t line_size)
: line_size(line_size)
{}
std::streamsize optimal_buffer_size() const
{
static constexpr int defaultBufferSize {1024};
return static_cast(
base64_encode_size(line_size != 0U ? line_size : defaultBufferSize)
);
}
template
void close(Device& dev)
{
if (pending_size) {
base64_encode(buffer, pending.data(), pending_size);
}
if (!buffer.empty()) {
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* str, std::streamsize n)
{
std::streamsize res = n;
if (pending_size > 0) {
while (n && pending_size < 3) {
pending[pending_size] = *str++;
++pending_size;
--n;
}
if (pending_size != 3) {
return res;
}
base64_encode(buffer, pending.data(), 3);
}
pending_size = n % 3;
n = n / 3 * 3;
base64_encode(buffer, str, n);
str += n;
for (unsigned i = 0; i < pending_size; ++i) {
pending[i] = str[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;
std::array pending {};
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
{
using char_type = char;
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
{
static constexpr int defaultBufferSize {1024};
return static_cast(
base64_encode_size(line_size != 0U ? line_size : defaultBufferSize)
);
}
template
std::streamsize read(Device& dev, char_type* str, std::streamsize n)
{
static auto table = base64_decode_table();
if (!n) {
return 0;
}
std::streamsize count = 0;
for (;;) {
while (pending_out < out_count) {
*str++ = char_array_3[pending_out++];
++count;
if (--n == 0) {
return count;
}
}
if (eof) {
return count ? count : -1;
}
for (;;) {
int newChar = bio::get(dev);
if (newChar < 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 decodedChar = table[newChar];
if (decodedChar < 0) {
if (decodedChar == -2 || errHandling == Base64ErrorHandling::silent) {
continue;
}
throw BOOST_IOSTREAMS_FAILURE("Invalid character in base64 string");
}
char_array_4[pending_in++] = (char)decodedChar;
}
if (pending_in == 4) {
pending_out = pending_in = 0;
char_array_3[0] = static_cast(
(char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4)
);
char_array_3[1] = static_cast(
((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2)
);
char_array_3[2] = static_cast(
((char_array_4[2] & 0x3) << 6) + char_array_4[3]
);
break;
}
}
}
}
std::size_t line_size;
std::uint8_t pending_in = 0;
std::array char_array_4 {};
std::uint8_t pending_out = 3;
std::uint8_t out_count = 3;
std::array char_array_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 = base64DefaultBufferSize
)
{
std::unique_ptr res(new bio::filtering_ostream);
auto* filteringStream = dynamic_cast(res.get());
filteringStream->push(base64_encoder(line_size));
filteringStream->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 = base64DefaultBufferSize
)
{
std::unique_ptr res(new bio::filtering_ostream);
auto* filteringStream = dynamic_cast(res.get());
filteringStream->push(base64_encoder(line_size));
filteringStream->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 = base64DefaultBufferSize,
Base64ErrorHandling errHandling = Base64ErrorHandling::silent
)
{
std::unique_ptr res(new bio::filtering_istream);
auto* filteringStream = dynamic_cast(res.get());
filteringStream->push(base64_decoder(line_size, errHandling));
filteringStream->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 = base64DefaultBufferSize,
Base64ErrorHandling errHandling = Base64ErrorHandling::silent
)
{
std::unique_ptr res(new bio::filtering_istream);
auto* filteringStream = dynamic_cast(res.get());
filteringStream->push(base64_decoder(line_size, errHandling));
filteringStream->push(bio::file_source(filepath));
return res;
}
} // namespace Base
// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic,
// cppcoreguidelines-pro-bounds-constant-array-index, cppcoreguidelines-avoid-magic-numbers,
// readability-magic-numbers)
#endif // FREECAD_BASE_BASE64FILTER_H