# HG changeset patch # User David Demelier # Date 1464790789 -7200 # Node ID 00f1789a49fa53cb4731d8f36c3ace2a3707798b # Parent cc50a80dc1631b35f38832a08e445380242c1c90 Base64: resurrection diff -r cc50a80dc163 -r 00f1789a49fa CMakeLists.txt --- a/CMakeLists.txt Wed Jun 01 16:18:36 2016 +0200 +++ b/CMakeLists.txt Wed Jun 01 16:19:49 2016 +0200 @@ -1,7 +1,7 @@ # # CMakeLists.txt -- code building for common code # -# Copyright (c) 2013-2015 David Demelier +# Copyright (c) 2013-2016 David Demelier # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above @@ -42,4 +42,5 @@ ) endif () +add_subdirectory(modules/base64) add_subdirectory(modules/options) diff -r cc50a80dc163 -r 00f1789a49fa modules/base64/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/base64/CMakeLists.txt Wed Jun 01 16:19:49 2016 +0200 @@ -0,0 +1,22 @@ +# +# CMakeLists.txt -- code building for common code +# +# Copyright (c) 2013-2016 David Demelier +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# + +code_define_module( + NAME base64 + SOURCES base64.hpp +) \ No newline at end of file diff -r cc50a80dc163 -r 00f1789a49fa modules/base64/base64.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/base64/base64.hpp Wed Jun 01 16:19:49 2016 +0200 @@ -0,0 +1,240 @@ +/* + * base64.hpp -- base64 encoding and decoding + * + * Copyright (c) 2013-2016 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef BASE64_HPP +#define BASE64_HPP + +/** + * \file base64.hpp + * \brief Base64 encoding and decoding. + * \author David Demelier + */ + +/** + * \page Base64 Base64 + * \brief Base64 encoding and decoding. + * + * The %base64 library let you encode and decode %base64 data from std::string and iterators. + * + * ## Encoding + * + * You can encode like this: + * + * ````cpp + * std::string b64 = base64::encode("Hello world!"); + * ```` + * + * ## Decoding + * + * And you can decode like this: + * + * ```` + * try { + * std::string text = base64::decode(msg); + * } catch (const std::exception &ex) { + * std::cerr << ex.what() << std::endl; + * } + * ```` + */ + +#include +#include +#include +#include + +/** + * \brief main %base64 namespace. + */ +namespace base64 { + +/** + * Check if the character is a %base64 character, A-Za-z0-9 and +/. + * + * \param ch the character to test + * \return true if valid + */ +inline bool isBase64(char ch) noexcept +{ + return std::isalnum(ch) != 0 || ch == '+' || ch == '/'; +} + +/** + * Check if the given character is a valid character in %base64 string, including '='. + * + * \param ch the character + * \return true if the character is valid + */ +inline bool isValid(char ch) noexcept +{ + return isBase64(ch) || ch == '='; +} + +/** + * Get the %base64 character from the 6-bits value. + * + * \pre value must be valid (< 64) + * \param value the value + * \return the %base64 character for value + */ +inline char lookup(unsigned char value) noexcept +{ + static const char *table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + assert(value < 64); + + return table[value]; +} + +/** + * Get the integer value from the %base64 character. + * + * \pre isBase64(ch) + * \param ch the %base64 character + * \return the integer value for the %base64 character ch + * ```` + * auto b64 = base64::rlookup('D') // 3 + * ```` + */ +inline unsigned char rlookup(char ch) noexcept +{ + assert(isBase64(ch)); + + if (ch >= '0' && ch <= '9') + return static_cast(ch + 4); + if (ch >= 'A' && ch <= 'Z') + return static_cast(ch - 65); + if (ch >= 'a' && ch <= 'z') + return static_cast(ch - 71); + + return (ch == '+') ? 62U : 63U; +} + +/** + * Encode the input to the output. + * + * Requirements: + * - **InputIt** must be [InputIterator](http://en.cppreference.com/w/cpp/concept/InputIterator) + * - **OutputIt** must be [OutputIterator](http://en.cppreference.com/w/cpp/concept/OutputIterator) + * + * \param input the beginning + * \param end the end of the data + * \param output the output destination + * \return output + */ +template +OutputIt encode(InputIt input, InputIt end, OutputIt output) +{ + while (input != end) { + char inputbuf[3] = { 0, 0, 0 }; + int count; + + for (count = 0; count < 3 && input != end; ++count) + inputbuf[count] = *input++; + + *output++ = lookup(inputbuf[0] >> 2 & 0x3f); + *output++ = lookup((inputbuf[0] << 4 & 0x3f) | (inputbuf[1] >> 4 & 0x0f)); + *output++ = (count < 2) ? '=' : lookup((inputbuf[1] << 2 & 0x3c) | (inputbuf[2] >> 6 & 0x03)); + *output++ = (count < 3) ? '=' : lookup(inputbuf[2] & 0x3f); + } + + return output; +} + +/** + * Decode the input to the output. + * + * Requirements: + * - **InputIt** must be [InputIterator](http://en.cppreference.com/w/cpp/concept/InputIterator) + * - **OutputIt** must be [OutputIterator](http://en.cppreference.com/w/cpp/concept/OutputIterator) + * + * \param input the beginning + * \param end the end of the data + * \param output the output destination + * \return output + * \throw std::invalid_argument on bad %base64 string + */ +template +OutputIt decode(InputIt input, InputIt end, OutputIt output) +{ + while (input != end) { + char inputbuf[4] = { -1, -1, -1, -1 }; + int count; + + for (count = 0; count < 4 && input != end; ++count) { + // Check if the character is valid and get its value. + if ((*input == '=' && count <= 1) || !isValid(*input)) + throw std::invalid_argument("invalid base64 string"); + if (isBase64(*input)) + inputbuf[count] = static_cast(rlookup(*input)); + + input++; + } + + if (count != 4) + throw std::invalid_argument("truncated string"); + + *output++ = static_cast(((inputbuf[0] << 2) & 0xfc) | ((inputbuf[1] >> 4) & 0x03)); + + if (inputbuf[2] != -1) + *output++ = static_cast(((inputbuf[1] << 4) & 0xf0) | ((inputbuf[2] >> 2) & 0x0f)); + if (inputbuf[3] != -1) { + // "XY=Z" is not allowed. + if (inputbuf[2] == -1) + throw std::invalid_argument("invalid base64 string"); + + *output++ = static_cast(((inputbuf[2] << 6) & 0xc0) | (inputbuf[3] & 0x3f)); + } + } + + return output; +} + +/** + * Encode a string. + * + * \param input the input string + * \return the %base64 formatted string + */ +inline std::string encode(const std::string &input) +{ + std::string result; + + encode(input.begin(), input.end(), std::back_inserter(result)); + + return result; +} + +/** + * Decode a string. + * + * \param input the %base64 formatted string + * \return the original string + * \throw std::invalid_argument on bad %base64 string + */ +inline std::string decode(const std::string &input) +{ + std::string result; + + decode(input.begin(), input.end(), std::back_inserter(result)); + + return result; +} + +} // !base64 + +#endif // !BASE64_HPP diff -r cc50a80dc163 -r 00f1789a49fa modules/base64/doc/mainpage.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/base64/doc/mainpage.cpp Wed Jun 01 16:19:49 2016 +0200 @@ -0,0 +1,26 @@ +/** + * \mainpage + * + * Welcome to the %base64 library. + * + * ## Introduction + * + * This code allows you to encode and decode %base64 data in C++ as easy as possible. + * + * ## Installation + * + * Just copy the file base64.hpp and add it to your project. + * + * ## Overview + * + * ```` + * #include "base64.hpp" + * + * int main(void) + * { + * base64::encode("Hello World!"); + * + * return 0; + * } + * ```` + */ diff -r cc50a80dc163 -r 00f1789a49fa modules/base64/test/main.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/base64/test/main.cpp Wed Jun 01 16:19:49 2016 +0200 @@ -0,0 +1,213 @@ +/* + * main.cpp -- main test file for base64 + * + * Copyright (c) 2013-2016 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include + +TEST(Lookup, lookup) +{ + ASSERT_EQ('A', base64::lookup(0b000000)); + ASSERT_EQ('B', base64::lookup(0b000001)); + ASSERT_EQ('C', base64::lookup(0b000010)); + ASSERT_EQ('D', base64::lookup(0b000011)); + ASSERT_EQ('E', base64::lookup(0b000100)); + ASSERT_EQ('F', base64::lookup(0b000101)); + ASSERT_EQ('G', base64::lookup(0b000110)); + ASSERT_EQ('H', base64::lookup(0b000111)); + ASSERT_EQ('I', base64::lookup(0b001000)); + ASSERT_EQ('J', base64::lookup(0b001001)); + ASSERT_EQ('K', base64::lookup(0b001010)); + ASSERT_EQ('L', base64::lookup(0b001011)); + ASSERT_EQ('M', base64::lookup(0b001100)); + ASSERT_EQ('N', base64::lookup(0b001101)); + ASSERT_EQ('O', base64::lookup(0b001110)); + ASSERT_EQ('P', base64::lookup(0b001111)); + ASSERT_EQ('Q', base64::lookup(0b010000)); + ASSERT_EQ('R', base64::lookup(0b010001)); + ASSERT_EQ('S', base64::lookup(0b010010)); + ASSERT_EQ('T', base64::lookup(0b010011)); + ASSERT_EQ('U', base64::lookup(0b010100)); + ASSERT_EQ('V', base64::lookup(0b010101)); + ASSERT_EQ('W', base64::lookup(0b010110)); + ASSERT_EQ('X', base64::lookup(0b010111)); + ASSERT_EQ('Y', base64::lookup(0b011000)); + ASSERT_EQ('Z', base64::lookup(0b011001)); + ASSERT_EQ('a', base64::lookup(0b011010)); + ASSERT_EQ('b', base64::lookup(0b011011)); + ASSERT_EQ('c', base64::lookup(0b011100)); + ASSERT_EQ('d', base64::lookup(0b011101)); + ASSERT_EQ('e', base64::lookup(0b011110)); + ASSERT_EQ('f', base64::lookup(0b011111)); + ASSERT_EQ('g', base64::lookup(0b100000)); + ASSERT_EQ('h', base64::lookup(0b100001)); + ASSERT_EQ('i', base64::lookup(0b100010)); + ASSERT_EQ('j', base64::lookup(0b100011)); + ASSERT_EQ('k', base64::lookup(0b100100)); + ASSERT_EQ('l', base64::lookup(0b100101)); + ASSERT_EQ('m', base64::lookup(0b100110)); + ASSERT_EQ('n', base64::lookup(0b100111)); + ASSERT_EQ('o', base64::lookup(0b101000)); + ASSERT_EQ('p', base64::lookup(0b101001)); + ASSERT_EQ('q', base64::lookup(0b101010)); + ASSERT_EQ('r', base64::lookup(0b101011)); + ASSERT_EQ('s', base64::lookup(0b101100)); + ASSERT_EQ('t', base64::lookup(0b101101)); + ASSERT_EQ('u', base64::lookup(0b101110)); + ASSERT_EQ('v', base64::lookup(0b101111)); + ASSERT_EQ('w', base64::lookup(0b110000)); + ASSERT_EQ('x', base64::lookup(0b110001)); + ASSERT_EQ('y', base64::lookup(0b110010)); + ASSERT_EQ('z', base64::lookup(0b110011)); + ASSERT_EQ('0', base64::lookup(0b110100)); + ASSERT_EQ('1', base64::lookup(0b110101)); + ASSERT_EQ('2', base64::lookup(0b110110)); + ASSERT_EQ('3', base64::lookup(0b110111)); + ASSERT_EQ('4', base64::lookup(0b111000)); + ASSERT_EQ('5', base64::lookup(0b111001)); + ASSERT_EQ('6', base64::lookup(0b111010)); + ASSERT_EQ('7', base64::lookup(0b111011)); + ASSERT_EQ('8', base64::lookup(0b111100)); + ASSERT_EQ('9', base64::lookup(0b111101)); + ASSERT_EQ('+', base64::lookup(0b111110)); + ASSERT_EQ('/', base64::lookup(0b111111)); +} + +TEST(Lookup, rlookup) +{ + ASSERT_EQ(0b000000U, base64::rlookup('A')); + ASSERT_EQ(0b000001U, base64::rlookup('B')); + ASSERT_EQ(0b000010U, base64::rlookup('C')); + ASSERT_EQ(0b000011U, base64::rlookup('D')); + ASSERT_EQ(0b000100U, base64::rlookup('E')); + ASSERT_EQ(0b000101U, base64::rlookup('F')); + ASSERT_EQ(0b000110U, base64::rlookup('G')); + ASSERT_EQ(0b000111U, base64::rlookup('H')); + ASSERT_EQ(0b001000U, base64::rlookup('I')); + ASSERT_EQ(0b001001U, base64::rlookup('J')); + ASSERT_EQ(0b001010U, base64::rlookup('K')); + ASSERT_EQ(0b001011U, base64::rlookup('L')); + ASSERT_EQ(0b001100U, base64::rlookup('M')); + ASSERT_EQ(0b001101U, base64::rlookup('N')); + ASSERT_EQ(0b001110U, base64::rlookup('O')); + ASSERT_EQ(0b001111U, base64::rlookup('P')); + ASSERT_EQ(0b010000U, base64::rlookup('Q')); + ASSERT_EQ(0b010001U, base64::rlookup('R')); + ASSERT_EQ(0b010010U, base64::rlookup('S')); + ASSERT_EQ(0b010011U, base64::rlookup('T')); + ASSERT_EQ(0b010100U, base64::rlookup('U')); + ASSERT_EQ(0b010101U, base64::rlookup('V')); + ASSERT_EQ(0b010110U, base64::rlookup('W')); + ASSERT_EQ(0b010111U, base64::rlookup('X')); + ASSERT_EQ(0b011000U, base64::rlookup('Y')); + ASSERT_EQ(0b011001U, base64::rlookup('Z')); + ASSERT_EQ(0b011010U, base64::rlookup('a')); + ASSERT_EQ(0b011011U, base64::rlookup('b')); + ASSERT_EQ(0b011100U, base64::rlookup('c')); + ASSERT_EQ(0b011101U, base64::rlookup('d')); + ASSERT_EQ(0b011110U, base64::rlookup('e')); + ASSERT_EQ(0b011111U, base64::rlookup('f')); + ASSERT_EQ(0b100000U, base64::rlookup('g')); + ASSERT_EQ(0b100001U, base64::rlookup('h')); + ASSERT_EQ(0b100010U, base64::rlookup('i')); + ASSERT_EQ(0b100011U, base64::rlookup('j')); + ASSERT_EQ(0b100100U, base64::rlookup('k')); + ASSERT_EQ(0b100101U, base64::rlookup('l')); + ASSERT_EQ(0b100110U, base64::rlookup('m')); + ASSERT_EQ(0b100111U, base64::rlookup('n')); + ASSERT_EQ(0b101000U, base64::rlookup('o')); + ASSERT_EQ(0b101001U, base64::rlookup('p')); + ASSERT_EQ(0b101010U, base64::rlookup('q')); + ASSERT_EQ(0b101011U, base64::rlookup('r')); + ASSERT_EQ(0b101100U, base64::rlookup('s')); + ASSERT_EQ(0b101101U, base64::rlookup('t')); + ASSERT_EQ(0b101110U, base64::rlookup('u')); + ASSERT_EQ(0b101111U, base64::rlookup('v')); + ASSERT_EQ(0b110000U, base64::rlookup('w')); + ASSERT_EQ(0b110001U, base64::rlookup('x')); + ASSERT_EQ(0b110010U, base64::rlookup('y')); + ASSERT_EQ(0b110011U, base64::rlookup('z')); + ASSERT_EQ(0b110100U, base64::rlookup('0')); + ASSERT_EQ(0b110101U, base64::rlookup('1')); + ASSERT_EQ(0b110110U, base64::rlookup('2')); + ASSERT_EQ(0b110111U, base64::rlookup('3')); + ASSERT_EQ(0b111000U, base64::rlookup('4')); + ASSERT_EQ(0b111001U, base64::rlookup('5')); + ASSERT_EQ(0b111010U, base64::rlookup('6')); + ASSERT_EQ(0b111011U, base64::rlookup('7')); + ASSERT_EQ(0b111100U, base64::rlookup('8')); + ASSERT_EQ(0b111101U, base64::rlookup('9')); + ASSERT_EQ(0b111110U, base64::rlookup('+')); + ASSERT_EQ(0b111111U, base64::rlookup('/')); +} + +TEST(Encode, basic) +{ + ASSERT_EQ("YQ==", base64::encode("a")); + ASSERT_EQ("YWI=", base64::encode("ab")); + ASSERT_EQ("YWJj", base64::encode("abc")); + ASSERT_EQ("aGVsbG8=", base64::encode("hello")); + ASSERT_EQ("dGhpcyBpcyBhIGxvbmcgc2VudGVuY2U=", base64::encode("this is a long sentence")); +} + +TEST(Decode, basic) +{ + ASSERT_EQ("a", base64::decode("YQ==")); + ASSERT_EQ("ab", base64::decode("YWI=")); + ASSERT_EQ("abc", base64::decode("YWJj")); + ASSERT_EQ("hello", base64::decode("aGVsbG8=")); + ASSERT_EQ("this is a long sentence", base64::decode("dGhpcyBpcyBhIGxvbmcgc2VudGVuY2U=")); + ASSERT_EQ("Welcome to our server dude", base64::decode("V2VsY29tZSB0byBvdXIgc2VydmVyIGR1ZGU=")); +} + +TEST(Failure, truncated) +{ + ASSERT_THROW(base64::decode("YW="), std::invalid_argument); +} + +TEST(Failure, invalid) +{ + ASSERT_THROW(base64::decode("?!"), std::invalid_argument); +} + +TEST(Failure, wrong1) +{ + ASSERT_THROW(base64::decode("=ABC"), std::invalid_argument); +} + +TEST(Failure, wrong2) +{ + ASSERT_THROW(base64::decode("A=BC"), std::invalid_argument); +} + +TEST(Failure, wrong3) +{ + ASSERT_THROW(base64::decode("==BC"), std::invalid_argument); +} + +TEST(Failure, wrong4) +{ + ASSERT_THROW(base64::decode("AB=C"), std::invalid_argument); +} + +int main(int argc, char **argv) +{ + testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +}