Add base64_url_unpadded variant for unpadded base64 encoding 24/head
authorGerman M. Bravo <german.mb@deipi.com>
Wed, 26 Oct 2016 15:18:35 +0000 (10:18 -0500)
committerGerman M. Bravo <german.mb@deipi.com>
Wed, 26 Oct 2016 15:18:35 +0000 (10:18 -0500)
README.md
cppcodec/base64_default_url_unpadded.hpp [new file with mode: 0644]
cppcodec/base64_url_unpadded.hpp [new file with mode: 0644]
test/test_cppcodec.cpp

index 62fcbd3..b8028e5 100644 (file)
--- a/README.md
+++ b/README.md
@@ -62,8 +62,9 @@ or `<cppcodec/hex_lower.hpp>`. Currently supported variants are:
 * `base64_url` is the same as `base64_rfc4648` (and defined in the same RFC)
   but uses '-' (minus) and '_' (underscore) as special characters instead of
   '+' and '/'. This is safe to use for URLs and file names. Padding with '=' is
-  still required, look for a future `base64_url_unpadded` variant if you want
-  to do without.
+  still required.
+* `base64_url_unpadded` variant is the same as `base64_url`, if you want
+  to do without padding (which, for this one, is optional).
 
 ### base32
 
diff --git a/cppcodec/base64_default_url_unpadded.hpp b/cppcodec/base64_default_url_unpadded.hpp
new file mode 100644 (file)
index 0000000..3173966
--- /dev/null
@@ -0,0 +1,31 @@
+/**
+ *  Copyright (C) 2016 Topology LP
+ *  All rights reserved.
+ *
+ *  Permission is hereby granted, free of charge, to any person obtaining a copy
+ *  of this software and associated documentation files (the "Software"), to
+ *  deal in the Software without restriction, including without limitation the
+ *  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ *  sell copies of the Software, and to permit persons to whom the Software is
+ *  furnished to do so, subject to the following conditions:
+ *
+ *  The above copyright notice and this permission notice shall be included in
+ *  all copies or substantial portions of the Software.
+ *
+ *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ *  IN THE SOFTWARE.
+ */
+
+#ifndef CPPCODEC_BASE64_DEFAULT_URL_UNPADDED
+#define CPPCODEC_BASE64_DEFAULT_URL_UNPADDED
+
+#include "base64_url_unpadded.hpp"
+
+using base64 = cppcodec::base64_url_unpadded;
+
+#endif // CPPCODEC_BASE64_DEFAULT_URL_UNPADDED
diff --git a/cppcodec/base64_url_unpadded.hpp b/cppcodec/base64_url_unpadded.hpp
new file mode 100644 (file)
index 0000000..d525cf1
--- /dev/null
@@ -0,0 +1,48 @@
+/**
+ *  Copyright (C) 2016 Topology LP
+ *  All rights reserved.
+ *
+ *  Permission is hereby granted, free of charge, to any person obtaining a copy
+ *  of this software and associated documentation files (the "Software"), to
+ *  deal in the Software without restriction, including without limitation the
+ *  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ *  sell copies of the Software, and to permit persons to whom the Software is
+ *  furnished to do so, subject to the following conditions:
+ *
+ *  The above copyright notice and this permission notice shall be included in
+ *  all copies or substantial portions of the Software.
+ *
+ *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ *  IN THE SOFTWARE.
+ */
+
+#ifndef CPPCODEC_BASE64_URL_UNPADDED
+#define CPPCODEC_BASE64_URL_UNPADDED
+
+#include "base64_url.hpp"
+
+namespace cppcodec {
+
+namespace detail {
+
+class base64_url_unpadded : public base64_url
+{
+public:
+    template <typename Codec> using codec_impl = stream_codec<Codec, base64_url_unpadded>;
+
+    static inline constexpr bool generates_padding() { return false; }
+    static inline constexpr bool requires_padding() { return false; }
+};
+
+} // namespace detail
+
+using base64_url_unpadded = detail::codec<detail::base64<detail::base64_url_unpadded>>;
+
+} // namespace cppcodec
+
+#endif // CPPCODEC_BASE64_URL_UNPADDED
index e07c63a..bbda970 100644 (file)
@@ -29,6 +29,7 @@
 #include <cppcodec/base32_rfc4648.hpp>
 #include <cppcodec/base64_rfc4648.hpp>
 #include <cppcodec/base64_url.hpp>
+#include <cppcodec/base64_url_unpadded.hpp>
 #include <cppcodec/hex_lower.hpp>
 #include <cppcodec/hex_upper.hpp>
 #include <stdint.h>
@@ -584,6 +585,110 @@ TEST_CASE("base64 (RFC 4648)", "[base64][rfc4648]") {
     }
 }
 
+TEST_CASE("base64 (unpadded URL-safe)", "[base64][url_unpadded]") {
+    using base64 = cppcodec::base64_url_unpadded;
+
+    SECTION("encoded size calculation") {
+        REQUIRE(base64::encoded_size(0) == 0);
+        REQUIRE(base64::encoded_size(1) == 2);
+        REQUIRE(base64::encoded_size(2) == 3);
+        REQUIRE(base64::encoded_size(3) == 4);
+        REQUIRE(base64::encoded_size(4) == 6);
+        REQUIRE(base64::encoded_size(5) == 7);
+        REQUIRE(base64::encoded_size(6) == 8);
+        REQUIRE(base64::encoded_size(7) == 10);
+        REQUIRE(base64::encoded_size(12) == 16);
+    }
+
+    SECTION("maximum decoded size calculation") {
+        REQUIRE(base64::decoded_max_size(0) == 0);
+        REQUIRE(base64::decoded_max_size(1) == 0);
+        REQUIRE(base64::decoded_max_size(2) == 1);
+        REQUIRE(base64::decoded_max_size(3) == 2);
+        REQUIRE(base64::decoded_max_size(4) == 3);
+        REQUIRE(base64::decoded_max_size(5) == 3);
+        REQUIRE(base64::decoded_max_size(6) == 4);
+        REQUIRE(base64::decoded_max_size(7) == 5);
+        REQUIRE(base64::decoded_max_size(8) == 6);
+        REQUIRE(base64::decoded_max_size(9) == 6);
+        REQUIRE(base64::decoded_max_size(10) == 7);
+        REQUIRE(base64::decoded_max_size(11) == 8);
+        REQUIRE(base64::decoded_max_size(12) == 9);
+        REQUIRE(base64::decoded_max_size(16) == 12);
+    }
+
+    SECTION("encoding data") {
+        REQUIRE(base64::encode(std::vector<uint8_t>()) == "");
+        REQUIRE(base64::encode(std::vector<uint8_t>({0})) == "AA");
+        REQUIRE(base64::encode(std::vector<uint8_t>({0, 0})) == "AAA");
+        REQUIRE(base64::encode(std::vector<uint8_t>({0, 0, 0})) == "AAAA");
+        REQUIRE(base64::encode(std::vector<uint8_t>({0, 0, 0, 0})) == "AAAAAA");
+        REQUIRE(base64::encode(std::vector<uint8_t>({0, 0, 0, 0, 0})) == "AAAAAAA");
+        REQUIRE(base64::encode(std::vector<uint8_t>({0, 0, 0, 0, 0, 0})) == "AAAAAAAA");
+
+        // RFC 4648: 9. Illustrations and Examples, adapted for more special characters
+        REQUIRE(base64::encode(std::vector<uint8_t>({0x14, 0xFB, 0xBF, 0x03, 0xD9, 0x7E})) == "FPu_A9l-");
+        REQUIRE(base64::encode(std::vector<uint8_t>({0x14, 0xFB, 0xBF, 0x03, 0xD9})) == "FPu_A9k");
+        REQUIRE(base64::encode(std::vector<uint8_t>({0x14, 0xFB, 0xBF, 0x03})) == "FPu_Aw");
+
+        // RFC 4648: 10. Test Vectors
+        REQUIRE(base64::encode(std::string("")) == "");
+        REQUIRE(base64::encode(std::string("f")) == "Zg");
+        REQUIRE(base64::encode(std::string("fo")) == "Zm8");
+        REQUIRE(base64::encode(std::string("foo")) == "Zm9v");
+        REQUIRE(base64::encode(std::string("foob")) == "Zm9vYg");
+        REQUIRE(base64::encode(std::string("fooba")) == "Zm9vYmE");
+        REQUIRE(base64::encode(std::string("foobar")) == "Zm9vYmFy");
+
+        // Other test strings.
+        REQUIRE(base64::encode(std::string("123")) == "MTIz");
+        REQUIRE(base64::encode(std::string("ABC")) == "QUJD");
+        REQUIRE(base64::encode(std::string("\xFF\xFF\xFF")) == "____");
+    }
+
+    SECTION("decoding data") {
+        REQUIRE(base64::decode("") == std::vector<uint8_t>());
+        REQUIRE(base64::decode("AA") == std::vector<uint8_t>({0}));
+        REQUIRE(base64::decode("AAA") == std::vector<uint8_t>({0, 0}));
+        REQUIRE(base64::decode("AAAA") == std::vector<uint8_t>({0, 0, 0}));
+        REQUIRE(base64::decode("AAAAAA") == std::vector<uint8_t>({0, 0, 0, 0}));
+        REQUIRE(base64::decode("AAAAAAA") == std::vector<uint8_t>({0, 0, 0, 0, 0}));
+        REQUIRE(base64::decode("AAAAAAAA") == std::vector<uint8_t>({0, 0, 0, 0, 0, 0}));
+
+        // RFC 4648: 9. Illustrations and Examples, adapted for more special characters
+        REQUIRE(base64::decode("FPu_A9l-") == std::vector<uint8_t>({0x14, 0xFB, 0xBF, 0x03, 0xD9, 0x7E}));
+        REQUIRE(base64::decode("FPu_A9k") == std::vector<uint8_t>({0x14, 0xFB, 0xBF, 0x03, 0xD9}));
+        REQUIRE(base64::decode("FPu_Aw") == std::vector<uint8_t>({0x14, 0xFB, 0xBF, 0x03}));
+
+        // RFC 4648: 10. Test Vectors
+        REQUIRE(base64::decode<std::string>("") == "");
+        REQUIRE(base64::decode<std::string>("Zg") == "f");
+        REQUIRE(base64::decode<std::string>("Zg==") == "f");
+        REQUIRE(base64::decode<std::string>("Zm8") == "fo");
+        REQUIRE(base64::decode<std::string>("Zm8=") == "fo");
+        REQUIRE(base64::decode<std::string>("Zm9v") == "foo");
+        REQUIRE(base64::decode<std::string>("Zm9vYg") == "foob");
+        REQUIRE(base64::decode<std::string>("Zm9vYg==") == "foob");
+        REQUIRE(base64::decode<std::string>("Zm9vYmE") == "fooba");
+        REQUIRE(base64::decode<std::string>("Zm9vYmE=") == "fooba");
+        REQUIRE(base64::decode<std::string>("Zm9vYmFy") == "foobar");
+
+        // Other test strings.
+        REQUIRE(base64::decode<std::string>("MTIz") == "123");
+        REQUIRE(base64::decode<std::string>("QUJD") == "ABC");
+        REQUIRE(base64::decode("____") == std::vector<uint8_t>({255, 255, 255}));
+
+        // An invalid number of symbols should throw the right kind of parse_error.
+        REQUIRE_THROWS_AS(base64::decode("A"), cppcodec::invalid_input_length&);
+        REQUIRE_THROWS_AS(base64::decode("AAAAA"), cppcodec::invalid_input_length&);
+
+        // An invalid symbol should throw a symbol error.
+        REQUIRE_THROWS_AS(base64::decode("A&B"), cppcodec::symbol_error&);
+        REQUIRE_THROWS_AS(base64::decode("++"), cppcodec::symbol_error&); // this is not standard base64
+        REQUIRE_THROWS_AS(base64::decode("//"), cppcodec::symbol_error&); // ...ditto
+    }
+}
+
 TEST_CASE("base64 (URL-safe)", "[base64][url]") {
     using base64 = cppcodec::base64_url;