Base64Encoder/Decoder: added support for 'base64url' encoding

Conflicts:
	Foundation/src/Base64Decoder.cpp
	Foundation/src/Base64Encoder.cpp
This commit is contained in:
Guenter Obiltschnig 2017-10-31 12:25:33 +01:00
parent 6273f52385
commit a9f0279382
6 changed files with 334 additions and 61 deletions

View File

@ -37,21 +37,25 @@ class Foundation_API Base64DecoderBuf: public UnbufferedStreamBuf
/// its streambuf.
{
public:
Base64DecoderBuf(std::istream& istr);
Base64DecoderBuf(std::istream& istr, int options = 0);
~Base64DecoderBuf();
private:
int readFromDevice();
int readOne();
int _options;
unsigned char _group[3];
int _groupLength;
int _groupIndex;
std::streambuf& _buf;
const unsigned char* _pInEncoding;
static unsigned char IN_ENCODING[256];
static bool IN_ENCODING_INIT;
static unsigned char IN_ENCODING_URL[256];
static bool IN_ENCODING_URL_INIT;
private:
Base64DecoderBuf(const Base64DecoderBuf&);
Base64DecoderBuf& operator = (const Base64DecoderBuf&);
@ -65,13 +69,13 @@ class Foundation_API Base64DecoderIOS: public virtual std::ios
/// order of the stream buffer and base classes.
{
public:
Base64DecoderIOS(std::istream& istr);
Base64DecoderIOS(std::istream& istr, int options = 0);
~Base64DecoderIOS();
Base64DecoderBuf* rdbuf();
protected:
Base64DecoderBuf _buf;
private:
Base64DecoderIOS(const Base64DecoderIOS&);
Base64DecoderIOS& operator = (const Base64DecoderIOS&);
@ -89,7 +93,7 @@ class Foundation_API Base64Decoder: public Base64DecoderIOS, public std::istream
/// its streambuf.
{
public:
Base64Decoder(std::istream& istr);
Base64Decoder(std::istream& istr, int options = 0);
~Base64Decoder();
private:

View File

@ -26,6 +26,19 @@
namespace Poco {
enum Base64EncodingOptions
{
BASE64_URL_ENCODING = 0x01,
/// Use the URL and filename-safe alphabet,
/// replacing '+' with '-' and '/' with '_'.
///
/// Will also set line length to unlimited.
BASE64_NO_PADDING = 0x02
/// Do not append padding characters ('=') at end.
};
class Foundation_API Base64EncoderBuf: public UnbufferedStreamBuf
/// This streambuf base64-encodes all data written
/// to it and forwards it to a connected
@ -37,9 +50,9 @@ class Foundation_API Base64EncoderBuf: public UnbufferedStreamBuf
/// not updated to match the buffer's state.
{
public:
Base64EncoderBuf(std::ostream& ostr);
Base64EncoderBuf(std::ostream& ostr, int options = 0);
~Base64EncoderBuf();
int close();
/// Closes the stream buffer.
@ -53,18 +66,21 @@ public:
int getLineLength() const;
/// Returns the currently set line length.
private:
int writeToDevice(char c);
int _options;
unsigned char _group[3];
int _groupLength;
int _pos;
int _lineLength;
std::streambuf& _buf;
const unsigned char* _pOutEncoding;
static const unsigned char OUT_ENCODING[64];
static const unsigned char OUT_ENCODING_URL[64];
friend class Base64DecoderBuf;
Base64EncoderBuf(const Base64EncoderBuf&);
@ -79,7 +95,7 @@ class Foundation_API Base64EncoderIOS: public virtual std::ios
/// order of the stream buffer and base classes.
{
public:
Base64EncoderIOS(std::ostream& ostr);
Base64EncoderIOS(std::ostream& ostr, int options = 0);
~Base64EncoderIOS();
int close();
Base64EncoderBuf* rdbuf();
@ -107,7 +123,7 @@ class Foundation_API Base64Encoder: public Base64EncoderIOS, public std::ostream
/// not updated to match the buffer's state.
{
public:
Base64Encoder(std::ostream& ostr);
Base64Encoder(std::ostream& ostr, int options = 0);
~Base64Encoder();
private:

View File

@ -23,6 +23,8 @@ namespace Poco {
unsigned char Base64DecoderBuf::IN_ENCODING[256];
bool Base64DecoderBuf::IN_ENCODING_INIT = false;
unsigned char Base64DecoderBuf::IN_ENCODING_URL[256];
bool Base64DecoderBuf::IN_ENCODING_URL_INIT = false;
namespace
@ -31,24 +33,45 @@ namespace
}
Base64DecoderBuf::Base64DecoderBuf(std::istream& istr):
Base64DecoderBuf::Base64DecoderBuf(std::istream& istr, int options):
_options(options),
_groupLength(0),
_groupIndex(0),
_buf(*istr.rdbuf())
_buf(*istr.rdbuf()),
_pInEncoding((options & BASE64_URL_ENCODING) ? IN_ENCODING_URL : IN_ENCODING)
{
FastMutex::ScopedLock lock(mutex);
if (!IN_ENCODING_INIT)
if (options & BASE64_URL_ENCODING)
{
for (unsigned i = 0; i < sizeof(IN_ENCODING); i++)
if (!IN_ENCODING_URL_INIT)
{
IN_ENCODING[i] = 0xFF;
for (unsigned i = 0; i < sizeof(IN_ENCODING_URL); i++)
{
IN_ENCODING_URL[i] = 0xFF;
}
for (unsigned i = 0; i < sizeof(Base64EncoderBuf::OUT_ENCODING_URL); i++)
{
IN_ENCODING_URL[Base64EncoderBuf::OUT_ENCODING_URL[i]] = i;
}
IN_ENCODING_URL[static_cast<unsigned char>('=')] = '\0';
IN_ENCODING_URL_INIT = true;
}
for (unsigned i = 0; i < sizeof(Base64EncoderBuf::OUT_ENCODING); i++)
}
else
{
if (!IN_ENCODING_INIT)
{
IN_ENCODING[Base64EncoderBuf::OUT_ENCODING[i]] = i;
for (unsigned i = 0; i < sizeof(IN_ENCODING); i++)
{
IN_ENCODING[i] = 0xFF;
}
for (unsigned i = 0; i < sizeof(Base64EncoderBuf::OUT_ENCODING); i++)
{
IN_ENCODING[Base64EncoderBuf::OUT_ENCODING[i]] = i;
}
IN_ENCODING[static_cast<unsigned char>('=')] = '\0';
IN_ENCODING_INIT = true;
}
IN_ENCODING[static_cast<unsigned char>('=')] = '\0';
IN_ENCODING_INIT = true;
}
}
@ -60,7 +83,7 @@ Base64DecoderBuf::~Base64DecoderBuf()
int Base64DecoderBuf::readFromDevice()
{
if (_groupIndex < _groupLength)
if (_groupIndex < _groupLength)
{
return _group[_groupIndex++];
}
@ -70,24 +93,40 @@ int Base64DecoderBuf::readFromDevice()
int c;
if ((c = readOne()) == -1) return -1;
buffer[0] = (unsigned char) c;
if (IN_ENCODING[buffer[0]] == 0xFF) throw DataFormatException();
if ((c = readOne()) == -1) throw DataFormatException();
if (_pInEncoding[buffer[0]] == 0xFF) throw DataFormatException();
if ((c = readOne()) == -1) return -1;
buffer[1] = (unsigned char) c;
if (IN_ENCODING[buffer[1]] == 0xFF) throw DataFormatException();
if ((c = readOne()) == -1) throw DataFormatException();
buffer[2] = c;
if (IN_ENCODING[buffer[2]] == 0xFF) throw DataFormatException();
if ((c = readOne()) == -1) throw DataFormatException();
buffer[3] = c;
if (IN_ENCODING[buffer[3]] == 0xFF) throw DataFormatException();
_group[0] = (IN_ENCODING[buffer[0]] << 2) | (IN_ENCODING[buffer[1]] >> 4);
_group[1] = ((IN_ENCODING[buffer[1]] & 0x0F) << 4) | (IN_ENCODING[buffer[2]] >> 2);
_group[2] = (IN_ENCODING[buffer[2]] << 6) | IN_ENCODING[buffer[3]];
if (_pInEncoding[buffer[1]] == 0xFF) throw DataFormatException();
if (_options & BASE64_NO_PADDING)
{
if ((c = readOne()) != -1)
buffer[2] = c;
else
buffer[2] = '=';
if (_pInEncoding[buffer[2]] == 0xFF) throw DataFormatException();
if ((c = readOne()) != -1)
buffer[3] = c;
else
buffer[3] = '=';
if (_pInEncoding[buffer[3]] == 0xFF) throw DataFormatException();
}
else
{
if ((c = readOne()) == -1) throw DataFormatException();
buffer[2] = c;
if (_pInEncoding[buffer[2]] == 0xFF) throw DataFormatException();
if ((c = readOne()) == -1) throw DataFormatException();
buffer[3] = c;
if (_pInEncoding[buffer[3]] == 0xFF) throw DataFormatException();
}
_group[0] = (_pInEncoding[buffer[0]] << 2) | (_pInEncoding[buffer[1]] >> 4);
_group[1] = ((_pInEncoding[buffer[1]] & 0x0F) << 4) | (_pInEncoding[buffer[2]] >> 2);
_group[2] = (_pInEncoding[buffer[2]] << 6) | _pInEncoding[buffer[3]];
if (buffer[2] == '=')
_groupLength = 1;
else if (buffer[3] == '=')
else if (buffer[3] == '=')
_groupLength = 2;
else
_groupLength = 3;
@ -100,13 +139,16 @@ int Base64DecoderBuf::readFromDevice()
int Base64DecoderBuf::readOne()
{
int ch = _buf.sbumpc();
while (ch == ' ' || ch == '\r' || ch == '\t' || ch == '\n')
ch = _buf.sbumpc();
if (!(_options & BASE64_URL_ENCODING))
{
while (ch == ' ' || ch == '\r' || ch == '\t' || ch == '\n')
ch = _buf.sbumpc();
}
return ch;
}
Base64DecoderIOS::Base64DecoderIOS(std::istream& istr): _buf(istr)
Base64DecoderIOS::Base64DecoderIOS(std::istream& istr, int options): _buf(istr, options)
{
poco_ios_init(&_buf);
}
@ -123,7 +165,7 @@ Base64DecoderBuf* Base64DecoderIOS::rdbuf()
}
Base64Decoder::Base64Decoder(std::istream& istr): Base64DecoderIOS(istr), std::istream(&_buf)
Base64Decoder::Base64Decoder(std::istream& istr, int options): Base64DecoderIOS(istr, options), std::istream(&_buf)
{
}

View File

@ -26,16 +26,31 @@ const unsigned char Base64EncoderBuf::OUT_ENCODING[64] =
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/'
};
Base64EncoderBuf::Base64EncoderBuf(std::ostream& ostr):
const unsigned char Base64EncoderBuf::OUT_ENCODING_URL[64] =
{
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '-', '_'
};
Base64EncoderBuf::Base64EncoderBuf(std::ostream& ostr, int options):
_options(options),
_groupLength(0),
_pos(0),
_lineLength(72),
_buf(*ostr.rdbuf())
_lineLength((options & BASE64_URL_ENCODING) ? 0 : 72),
_buf(*ostr.rdbuf()),
_pOutEncoding((options & BASE64_URL_ENCODING) ? OUT_ENCODING_URL : OUT_ENCODING)
{
}
@ -73,15 +88,15 @@ int Base64EncoderBuf::writeToDevice(char c)
{
unsigned char idx;
idx = _group[0] >> 2;
if (_buf.sputc(OUT_ENCODING[idx]) == eof) return eof;
if (_buf.sputc(_pOutEncoding[idx]) == eof) return eof;
idx = ((_group[0] & 0x03) << 4) | (_group[1] >> 4);
if (_buf.sputc(OUT_ENCODING[idx]) == eof) return eof;
if (_buf.sputc(_pOutEncoding[idx]) == eof) return eof;
idx = ((_group[1] & 0x0F) << 2) | (_group[2] >> 6);
if (_buf.sputc(OUT_ENCODING[idx]) == eof) return eof;
if (_buf.sputc(_pOutEncoding[idx]) == eof) return eof;
idx = _group[2] & 0x3F;
if (_buf.sputc(OUT_ENCODING[idx]) == eof) return eof;
if (_buf.sputc(_pOutEncoding[idx]) == eof) return eof;
_pos += 4;
if (_lineLength > 0 && _pos >= _lineLength)
if (_lineLength > 0 && _pos >= _lineLength)
{
if (_buf.sputc('\r') == eof) return eof;
if (_buf.sputc('\n') == eof) return eof;
@ -103,30 +118,36 @@ int Base64EncoderBuf::close()
_group[1] = 0;
unsigned char idx;
idx = _group[0] >> 2;
if (_buf.sputc(OUT_ENCODING[idx]) == eof) return eof;
if (_buf.sputc(_pOutEncoding[idx]) == eof) return eof;
idx = ((_group[0] & 0x03) << 4) | (_group[1] >> 4);
if (_buf.sputc(OUT_ENCODING[idx]) == eof) return eof;
if (_buf.sputc('=') == eof) return eof;
if (_buf.sputc('=') == eof) return eof;
if (_buf.sputc(_pOutEncoding[idx]) == eof) return eof;
if (!(_options & BASE64_NO_PADDING))
{
if (_buf.sputc('=') == eof) return eof;
if (_buf.sputc('=') == eof) return eof;
}
}
else if (_groupLength == 2)
{
_group[2] = 0;
unsigned char idx;
idx = _group[0] >> 2;
if (_buf.sputc(OUT_ENCODING[idx]) == eof) return eof;
if (_buf.sputc(_pOutEncoding[idx]) == eof) return eof;
idx = ((_group[0] & 0x03) << 4) | (_group[1] >> 4);
if (_buf.sputc(OUT_ENCODING[idx]) == eof) return eof;
if (_buf.sputc(_pOutEncoding[idx]) == eof) return eof;
idx = ((_group[1] & 0x0F) << 2) | (_group[2] >> 6);
if (_buf.sputc(OUT_ENCODING[idx]) == eof) return eof;
if (_buf.sputc('=') == eof) return eof;
if (_buf.sputc(_pOutEncoding[idx]) == eof) return eof;
if (!(_options & BASE64_NO_PADDING))
{
if (_buf.sputc('=') == eof) return eof;
}
}
_groupLength = 0;
return _buf.pubsync();
}
Base64EncoderIOS::Base64EncoderIOS(std::ostream& ostr): _buf(ostr)
Base64EncoderIOS::Base64EncoderIOS(std::ostream& ostr, int options): _buf(ostr, options)
{
poco_ios_init(&_buf);
}
@ -149,7 +170,7 @@ Base64EncoderBuf* Base64EncoderIOS::rdbuf()
}
Base64Encoder::Base64Encoder(std::ostream& ostr): Base64EncoderIOS(ostr), std::ostream(&_buf)
Base64Encoder::Base64Encoder(std::ostream& ostr, int options): Base64EncoderIOS(ostr, options), std::ostream(&_buf)
{
}

View File

@ -55,6 +55,79 @@ void Base64Test::testEncoder()
encoder.close();
assert (str.str() == "QUJDREVG");
}
{
std::ostringstream str;
Base64Encoder encoder(str);
encoder << "!@#$%^&*()_~<>";
encoder.close();
assert (str.str() == "IUAjJCVeJiooKV9+PD4=");
}
}
void Base64Test::testEncoderURL()
{
{
std::ostringstream str;
Base64Encoder encoder(str, Poco::BASE64_URL_ENCODING);
encoder << std::string("\00\01\02\03\04\05", 6);
encoder.close();
assert (str.str() == "AAECAwQF");
}
{
std::ostringstream str;
Base64Encoder encoder(str, Poco::BASE64_URL_ENCODING);
encoder << std::string("\00\01\02\03", 4);
encoder.close();
assert (str.str() == "AAECAw==");
}
{
std::ostringstream str;
Base64Encoder encoder(str, Poco::BASE64_URL_ENCODING);
encoder << "ABCDEF";
encoder.close();
assert (str.str() == "QUJDREVG");
}
{
std::ostringstream str;
Base64Encoder encoder(str, Poco::BASE64_URL_ENCODING);
encoder << "!@#$%^&*()_~<>";
encoder.close();
assert (str.str() == "IUAjJCVeJiooKV9-PD4=");
}
}
void Base64Test::testEncoderNoPadding()
{
{
std::ostringstream str;
Base64Encoder encoder(str, Poco::BASE64_URL_ENCODING | Poco::BASE64_NO_PADDING);
encoder << std::string("\00\01\02\03\04\05", 6);
encoder.close();
assert (str.str() == "AAECAwQF");
}
{
std::ostringstream str;
Base64Encoder encoder(str, Poco::BASE64_URL_ENCODING | Poco::BASE64_NO_PADDING);
encoder << std::string("\00\01\02\03", 4);
encoder.close();
assert (str.str() == "AAECAw");
}
{
std::ostringstream str;
Base64Encoder encoder(str, Poco::BASE64_URL_ENCODING | Poco::BASE64_NO_PADDING);
encoder << "ABCDEF";
encoder.close();
assert (str.str() == "QUJDREVG");
}
{
std::ostringstream str;
Base64Encoder encoder(str, Poco::BASE64_URL_ENCODING | Poco::BASE64_NO_PADDING);
encoder << "!@#$%^&*()_~<>";
encoder.close();
assert (str.str() == "IUAjJCVeJiooKV9-PD4");
}
}
@ -125,6 +198,115 @@ void Base64Test::testDecoder()
}
void Base64Test::testDecoderURL()
{
{
std::istringstream istr("AAECAwQF");
Base64Decoder decoder(istr, Poco::BASE64_URL_ENCODING);
assert (decoder.good() && decoder.get() == 0);
assert (decoder.good() && decoder.get() == 1);
assert (decoder.good() && decoder.get() == 2);
assert (decoder.good() && decoder.get() == 3);
assert (decoder.good() && decoder.get() == 4);
assert (decoder.good() && decoder.get() == 5);
assert (decoder.good() && decoder.get() == -1);
}
{
std::istringstream istr("AAECAwQ=");
Base64Decoder decoder(istr, Poco::BASE64_URL_ENCODING);
assert (decoder.good() && decoder.get() == 0);
assert (decoder.good() && decoder.get() == 1);
assert (decoder.good() && decoder.get() == 2);
assert (decoder.good() && decoder.get() == 3);
assert (decoder.good() && decoder.get() == 4);
assert (decoder.good() && decoder.get() == -1);
}
{
std::istringstream istr("AAECAw==", Poco::BASE64_URL_ENCODING);
Base64Decoder decoder(istr);
assert (decoder.good() && decoder.get() == 0);
assert (decoder.good() && decoder.get() == 1);
assert (decoder.good() && decoder.get() == 2);
assert (decoder.good() && decoder.get() == 3);
assert (decoder.good() && decoder.get() == -1);
}
{
std::istringstream istr("QUJDREVG", Poco::BASE64_URL_ENCODING);
Base64Decoder decoder(istr);
std::string s;
decoder >> s;
assert (s == "ABCDEF");
assert (decoder.eof());
assert (!decoder.fail());
}
{
std::istringstream istr("QUJ\r\nDRE\r\nVG");
Base64Decoder decoder(istr, Poco::BASE64_URL_ENCODING);
std::string s;
decoder >> s;
assert (decoder.bad());
}
{
std::istringstream istr("QUJD#REVG");
Base64Decoder decoder(istr, Poco::BASE64_URL_ENCODING);
std::string s;
try
{
decoder >> s;
assert (decoder.bad());
}
catch (DataFormatException&)
{
}
assert (!decoder.eof());
}
{
std::istringstream istr("IUAjJCVeJiooKV9-PD4=");
Base64Decoder decoder(istr, Poco::BASE64_URL_ENCODING);
std::string s;
decoder >> s;
assert (s == "!@#$%^&*()_~<>");
assert (decoder.eof());
assert (!decoder.fail());
}
}
void Base64Test::testDecoderNoPadding()
{
{
std::istringstream istr("AAECAwQF");
Base64Decoder decoder(istr, Poco::BASE64_NO_PADDING);
assert (decoder.good() && decoder.get() == 0);
assert (decoder.good() && decoder.get() == 1);
assert (decoder.good() && decoder.get() == 2);
assert (decoder.good() && decoder.get() == 3);
assert (decoder.good() && decoder.get() == 4);
assert (decoder.good() && decoder.get() == 5);
assert (decoder.good() && decoder.get() == -1);
}
{
std::istringstream istr("AAECAwQ");
Base64Decoder decoder(istr, Poco::BASE64_NO_PADDING);
assert (decoder.good() && decoder.get() == 0);
assert (decoder.good() && decoder.get() == 1);
assert (decoder.good() && decoder.get() == 2);
assert (decoder.good() && decoder.get() == 3);
assert (decoder.good() && decoder.get() == 4);
assert (decoder.good() && decoder.get() == -1);
}
{
std::istringstream istr("AAECAw");
Base64Decoder decoder(istr, Poco::BASE64_NO_PADDING);
assert (decoder.good() && decoder.get() == 0);
assert (decoder.good() && decoder.get() == 1);
assert (decoder.good() && decoder.get() == 2);
assert (decoder.good() && decoder.get() == 3);
assert (decoder.good() && decoder.get() == -1);
}
}
void Base64Test::testEncodeDecode()
{
{
@ -170,7 +352,11 @@ CppUnit::Test* Base64Test::suite()
CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("Base64Test");
CppUnit_addTest(pSuite, Base64Test, testEncoder);
CppUnit_addTest(pSuite, Base64Test, testEncoderURL);
CppUnit_addTest(pSuite, Base64Test, testEncoderNoPadding);
CppUnit_addTest(pSuite, Base64Test, testDecoder);
CppUnit_addTest(pSuite, Base64Test, testDecoderURL);
CppUnit_addTest(pSuite, Base64Test, testDecoderNoPadding);
CppUnit_addTest(pSuite, Base64Test, testEncodeDecode);
return pSuite;

View File

@ -25,7 +25,11 @@ public:
~Base64Test();
void testEncoder();
void testEncoderURL();
void testEncoderNoPadding();
void testDecoder();
void testDecoderURL();
void testDecoderNoPadding();
void testEncodeDecode();
void setUp();