From 3d9bac43da5937235c962a53e68475f796c370aa Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Mon, 2 May 2022 15:46:07 -0400 Subject: Add internal Pl_Base64 Bidirectional base64; will be used by JSON v2. --- libqpdf/CMakeLists.txt | 1 + libqpdf/Pl_Base64.cc | 191 ++++++++++++++++++++++++++++++++++++++++++++++ libqpdf/qpdf/Pl_Base64.hh | 30 ++++++++ 3 files changed, 222 insertions(+) create mode 100644 libqpdf/Pl_Base64.cc create mode 100644 libqpdf/qpdf/Pl_Base64.hh (limited to 'libqpdf') diff --git a/libqpdf/CMakeLists.txt b/libqpdf/CMakeLists.txt index 305977de..72b87975 100644 --- a/libqpdf/CMakeLists.txt +++ b/libqpdf/CMakeLists.txt @@ -35,6 +35,7 @@ set(libqpdf_SOURCES Pl_AES_PDF.cc Pl_ASCII85Decoder.cc Pl_ASCIIHexDecoder.cc + Pl_Base64.cc Pl_Buffer.cc Pl_Concatenate.cc Pl_Count.cc diff --git a/libqpdf/Pl_Base64.cc b/libqpdf/Pl_Base64.cc new file mode 100644 index 00000000..bfacc1db --- /dev/null +++ b/libqpdf/Pl_Base64.cc @@ -0,0 +1,191 @@ +#include + +#include +#include +#include +#include +#include + +static char +to_c(unsigned int ch) +{ + return static_cast(ch); +} + +static unsigned char +to_uc(int ch) +{ + return static_cast(ch); +} + +static int +to_i(int i) +{ + return static_cast(i); +} + +Pl_Base64::Pl_Base64(char const* identifier, Pipeline* next, action_e action) : + Pipeline(identifier, next), + action(action), + pos(0), + end_of_data(false), + finished(false) +{ + reset(); +} + +void +Pl_Base64::write(unsigned char* data, size_t len) +{ + if (finished) { + throw std::logic_error("Pl_Base64 used after finished"); + } + if (this->action == a_decode) { + decode(data, len); + } else { + encode(data, len); + } +} + +void +Pl_Base64::decode(unsigned char* data, size_t len) +{ + unsigned char* p = data; + while (len > 0) { + if (!QUtil::is_space(to_c(*p))) { + this->buf[this->pos++] = *p; + if (this->pos == 4) { + flush(); + } + } + ++p; + --len; + } +} + +void +Pl_Base64::encode(unsigned char* data, size_t len) +{ + unsigned char* p = data; + while (len > 0) { + this->buf[this->pos++] = *p; + if (this->pos == 3) { + flush(); + } + ++p; + --len; + } +} + +void +Pl_Base64::flush() +{ + if (this->action == a_decode) { + flush_decode(); + } else { + flush_encode(); + } + reset(); +} + +void +Pl_Base64::flush_decode() +{ + if (this->end_of_data) { + throw std::runtime_error( + getIdentifier() + ": base64 decode: data follows pad characters"); + } + int pad = 0; + int shift = 18; + int outval = 0; + for (size_t i = 0; i < 4; ++i) { + int v = 0; + char ch = to_c(this->buf[i]); + if ((ch >= 'A') && (ch <= 'Z')) { + v = ch - 'A'; + } else if ((ch >= 'a') && (ch <= 'z')) { + v = ch - 'a' + 26; + } else if ((ch >= '0') && (ch <= '9')) { + v = ch - '0' + 52; + } else if ((ch == '+') || (ch == '-')) { + v = 62; + } else if ((ch == '/') || (ch == '_')) { + v = 63; + } else if ( + (ch == '=') && ((i == 3) || ((i == 2) && (this->buf[3] == '=')))) { + ++pad; + this->end_of_data = true; + v = 0; + } else { + throw std::runtime_error( + getIdentifier() + ": base64 decode: invalid input"); + } + outval |= v << shift; + shift -= 6; + } + unsigned char out[3] = { + to_uc(outval >> 16), + to_uc(0xff & (outval >> 8)), + to_uc(0xff & outval), + }; + + getNext()->write(out, QIntC::to_size(3 - pad)); +} + +void +Pl_Base64::flush_encode() +{ + int outval = ((this->buf[0] << 16) | (this->buf[1] << 8) | (this->buf[2])); + unsigned char out[4] = { + to_uc(outval >> 18), + to_uc(0x3f & (outval >> 12)), + to_uc(0x3f & (outval >> 6)), + to_uc(0x3f & outval), + }; + for (size_t i = 0; i < 4; ++i) { + int ch = to_i(out[i]); + if (ch < 26) { + ch += 'A'; + } else if (ch < 52) { + ch -= 26; + ch += 'a'; + } else if (ch < 62) { + ch -= 52; + ch += '0'; + } else if (ch == 62) { + ch = '+'; + } else if (ch == 63) { + ch = '/'; + } + out[i] = to_uc(ch); + } + for (size_t i = 0; i < 3 - this->pos; ++i) { + out[3 - i] = '='; + } + getNext()->write(out, 4); +} + +void +Pl_Base64::finish() +{ + if (this->pos > 0) { + if (finished) { + throw std::logic_error("Pl_Base64 used after finished"); + } + if (this->action == a_decode) { + for (size_t i = this->pos; i < 4; ++i) { + this->buf[i] = '='; + } + } + flush(); + } + this->finished = true; + getNext()->finish(); +} + +void +Pl_Base64::reset() +{ + this->pos = 0; + memset(buf, 0, 4); +} diff --git a/libqpdf/qpdf/Pl_Base64.hh b/libqpdf/qpdf/Pl_Base64.hh new file mode 100644 index 00000000..313bd2cb --- /dev/null +++ b/libqpdf/qpdf/Pl_Base64.hh @@ -0,0 +1,30 @@ +#ifndef PL_BASE64_HH +#define PL_BASE64_HH + +#include + +class Pl_Base64: public Pipeline +{ + public: + enum action_e { a_encode, a_decode }; + Pl_Base64(char const* identifier, Pipeline* next, action_e); + virtual ~Pl_Base64() = default; + virtual void write(unsigned char* buf, size_t len) override; + virtual void finish() override; + + private: + void decode(unsigned char* buf, size_t len); + void encode(unsigned char* buf, size_t len); + void flush(); + void flush_decode(); + void flush_encode(); + void reset(); + + action_e action; + unsigned char buf[4]; + size_t pos; + bool end_of_data; + bool finished; +}; + +#endif // PL_BASE64_HH -- cgit v1.2.3-54-g00ecf