changeset 517:00f1789a49fa

Base64: resurrection
author David Demelier <markand@malikania.fr>
date Wed, 01 Jun 2016 16:19:49 +0200
parents cc50a80dc163
children 78f296a7b2e5
files CMakeLists.txt modules/base64/CMakeLists.txt modules/base64/base64.hpp modules/base64/doc/mainpage.cpp modules/base64/test/main.cpp
diffstat 5 files changed, 503 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- 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 <markand@malikania.fr>
+# Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
 #
 # 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)
--- /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 <markand@malikania.fr>
+#
+# 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
--- /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 <markand@malikania.fr>
+ *
+ * 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 <markand@malikania.fr>
+ */
+
+/**
+ * \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 <cassert>
+#include <cctype>
+#include <stdexcept>
+#include <string>
+
+/**
+ * \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<unsigned char>(ch + 4);
+	if (ch >= 'A' && ch <= 'Z')
+		return static_cast<unsigned char>(ch - 65);
+	if (ch >= 'a' && ch <= 'z')
+		return static_cast<unsigned char>(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 <typename InputIt, typename OutputIt>
+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 <typename InputIt, typename OutputIt>
+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<char>(rlookup(*input));
+
+			input++;
+		}
+
+		if (count != 4)
+			throw std::invalid_argument("truncated string");
+
+		*output++ = static_cast<char>(((inputbuf[0] << 2) & 0xfc) | ((inputbuf[1] >> 4) & 0x03));
+
+		if (inputbuf[2] != -1)
+			*output++ = static_cast<char>(((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<char>(((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
--- /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;
+ * }
+ * ````
+ */
--- /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 <markand@malikania.fr>
+ *
+ * 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 <gtest/gtest.h>
+
+#include <base64.hpp>
+
+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();
+}