From 7806c39ecc92055cd338749148fda51b2dc01691 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Mon, 25 Apr 2016 12:39:27 +0300 Subject: Add base64_encode(), base64_decode() --- NEWS | 2 + butl/base64 | 48 +++++++++++ butl/base64.cxx | 211 ++++++++++++++++++++++++++++++++++++++++++++++++ butl/buildfile | 1 + tests/base64/buildfile | 7 ++ tests/base64/driver.cxx | 106 ++++++++++++++++++++++++ tests/buildfile | 2 +- 7 files changed, 376 insertions(+), 1 deletion(-) create mode 100644 butl/base64 create mode 100644 butl/base64.cxx create mode 100644 tests/base64/buildfile create mode 100644 tests/base64/driver.cxx diff --git a/NEWS b/NEWS index c459750..4db1de2 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,8 @@ Version 0.4.0 * Add temp_directory(), temp_path() utility functions. + * Add base64_encode(), base64_decode() utility functions. + Version 0.3.0 * Add SHA256 hash calculator based on code from the FreeBSD project. That diff --git a/butl/base64 b/butl/base64 new file mode 100644 index 0000000..7a0e999 --- /dev/null +++ b/butl/base64 @@ -0,0 +1,48 @@ +// file : butl/base64 -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BUTL_BASE64 +#define BUTL_BASE64 + +#include +#include +#include + +namespace butl +{ + // Base64-encode a stream or a buffer. Split the output into 76 char-long + // lines (new line is the 77th). If reading from a stream, check if it has + // badbit, failbit, or eofbit set and throw invalid_argument if that's the + // case. Otherwise, set eofbit on completion. If writing to a stream, check + // if it has badbit, failbit, or eofbit set and throw invalid_argument if + // that's the case. Otherwise set badbit if the write operation fails. + // + void + base64_encode (std::ostream&, std::istream&); + + std::string + base64_encode (std::istream&); + + std::string + base64_encode (const std::vector&); + + // Base64-decode a stream or a string. Throw invalid_argument if the input + // is not a valid base64 representation. If reading from a stream, check if + // it has badbit, failbit, or eofbit set and throw invalid_argument if + // that's the case. Otherwise, set eofbit on completion. If writing to a + // stream, check if it has badbit, failbit, or eofbit set and throw + // invalid_argument if that's the case. Otherwise set badbit if the write + // operation fails. + // + void + base64_decode (std::ostream&, std::istream&); + + void + base64_decode (std::ostream&, const std::string&); + + std::vector + base64_decode (const std::string&); +}; + +#endif // BUTL_BASE64 diff --git a/butl/base64.cxx b/butl/base64.cxx new file mode 100644 index 0000000..2939b46 --- /dev/null +++ b/butl/base64.cxx @@ -0,0 +1,211 @@ +// file : butl/base64.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include // size_t +#include +#include +#include // {istreambuf, ostreambuf, back_insert}_iterator +#include // invalid_argument + +using namespace std; + +namespace butl +{ + static const char* codes = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + // base64-encode the data in the iterator range [i, e). Write the encoded + // data starting at the iterator position o. + // + template + static void + base64_encode (I& i, const I& e, O& o) + { + const size_t un (65); // Non-existing index of the codes string. + for (size_t n (0); i != e; ++n) + { + if (n && n % 19 == 0) + *o++ = '\n'; // Split into lines, like the base64 utility does. + + char c (*i++); + size_t i1 ((c >> 2) & 0x3F); + size_t i2 ((c << 4) & 0x30); + + size_t i3 (un); + if (i != e) + { + c = *i++; + i2 |= (c >> 4) & 0xF; + i3 = (c << 2) & 0x3C; + } + + size_t i4 (un); + if (i != e) + { + c = *i++; + i3 |= (c >> 6) & 0x3; + i4 = c & 0x3F; + } + + *o++ = codes[i1]; + *o++ = codes[i2]; + *o++ = i3 == un ? '=' : codes[i3]; + *o++ = i4 == un ? '=' : codes[i4]; + } + } + + static char + index (char c) + { + switch (c) + { + case '/': return 63; + case '+': return 62; + default: + { + if (c >= 'A' && c <= 'Z') + return c - 'A'; + else if (c >= 'a' && c <= 'z') + return c - 'a' + 26; + else if (c >= '0' && c <= '9') + return c - '0' + 52; + else + throw invalid_argument ("invalid input"); + } + } + } + + // base64-decode the data in the iterator range [i, e). Write the decoded + // data starting at the iterator position o. Throw invalid_argument if the + // input data is invalid. + // + template + static void + base64_decode (I& i, const I& e, O& o) + { + auto bad = []() {throw invalid_argument ("invalid input");}; + + auto next = [&i, &e, &bad]() -> char + { + if (i == e) + bad (); + return *i++; + }; + + while (i != e) + { + char c (next ()); + if (c == '\n') // @@ Should we check for '\r' as well ? + continue; + + char i1 = index (c); + char i2 = index (next ()); + *o++ = (i1 << 2) | (i2 >> 4); + + c = next (); + if (c == '=') + { + if (next () != '=' || i != e) + bad (); + } + else + { + char i3 = index (c); + *o++ = (i2 << 4) | (i3 >> 2); + + c = next (); + if (c == '=') + { + if (i != e) + bad (); + } + else + *o++ = (i3 << 6) | index (c); + } + } + } + + string + base64_encode (istream& is) + { + if (!is.good ()) + throw invalid_argument ("bad stream"); + + string r; + istreambuf_iterator i (is); + back_insert_iterator o (r); + + base64_encode (i, istreambuf_iterator (), o); + is.setstate (istream::eofbit); + return r; + } + + void + base64_encode (ostream& os, istream& is) + { + if (!os.good () || !is.good ()) + throw invalid_argument ("bad stream"); + + istreambuf_iterator i (is); + ostreambuf_iterator o (os); + base64_encode (i, istreambuf_iterator (), o); + + if (o.failed ()) + os.setstate (istream::badbit); + + is.setstate (istream::eofbit); + } + + std::string + base64_encode (const std::vector& v) + { + string r; + back_insert_iterator o (r); + auto i (v.begin ()); + base64_encode (i, v.end (), o); + return r; + } + + void + base64_decode (ostream& os, istream& is) + { + if (!os.good () || !is.good ()) + throw invalid_argument ("bad stream"); + + istreambuf_iterator i (is); + ostreambuf_iterator o (os); + base64_decode (i, istreambuf_iterator (), o); + + if (o.failed ()) + os.setstate (istream::badbit); + + is.setstate (istream::eofbit); + } + + void + base64_decode (ostream& os, const string& s) + { + if (!os.good ()) + throw invalid_argument ("bad stream"); + + ostreambuf_iterator o (os); + auto i (s.cbegin ()); + base64_decode (i, s.cend (), o); + + if (o.failed ()) + os.setstate (istream::badbit); + } + + vector + base64_decode (const string& s) + { + vector r; + back_insert_iterator> o (r); + auto i (s.cbegin ()); + base64_decode (i, s.cend (), o); + return r; + } +} diff --git a/butl/buildfile b/butl/buildfile index bbec7a9..d214fd3 100644 --- a/butl/buildfile +++ b/butl/buildfile @@ -3,6 +3,7 @@ # license : MIT; see accompanying LICENSE file lib{butl}: \ +{hxx cxx}{ base64 } \ {hxx cxx}{ char-scanner } \ {hxx cxx}{ fdstream } \ {hxx ixx cxx}{ filesystem } \ diff --git a/tests/base64/buildfile b/tests/base64/buildfile new file mode 100644 index 0000000..63212fa --- /dev/null +++ b/tests/base64/buildfile @@ -0,0 +1,7 @@ +# file : tests/base64/buildfile +# copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +exe{driver}: cxx{driver} ../../butl/lib{butl} + +include ../../butl/ diff --git a/tests/base64/driver.cxx b/tests/base64/driver.cxx new file mode 100644 index 0000000..e43e04a --- /dev/null +++ b/tests/base64/driver.cxx @@ -0,0 +1,106 @@ +// file : tests/base64/driver.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include +#include +#include + +#include + +using namespace std; +using namespace butl; + +static bool +encode (const string& i, const string& o) +{ + // Test encoding. + // + istringstream is (i); + string s (base64_encode (is)); + bool r (s == o && is.eof ()); + + if (r) + { + is.seekg (0); + ostringstream os; + base64_encode (os, is); + r = os.str () == o && is.eof (); + } + + if (r) + r = base64_encode (vector (i.begin (), i.end ())) == o; + + // Test decoding. + // + if (r) + { + istringstream is (o); + ostringstream os; + base64_decode (os, is); + r = os.str () == i; + } + + if (r) + { + ostringstream os; + base64_decode (os, o); + r = os.str () == i; + } + + if (r) + { + vector v (base64_decode (o)); + r = string (v.begin (), v.end ()) == i; + } + + return r; +} + +int +main () +{ + assert (encode ("", "")); + assert (encode ("B", "Qg==")); + assert (encode ("BX", "Qlg=")); + assert (encode ("BXz", "Qlh6")); + assert (encode ("BXzS", "Qlh6Uw==")); + assert (encode ("BXzS@", "Qlh6U0A=")); + assert (encode ("BXzS@#", "Qlh6U0Aj")); + assert (encode ("BXzS@#/", "Qlh6U0AjLw==")); + + const char* s (R"delim( +class fdstream_base +{ +protected: + fdstream_base () = default; + fdstream_base (int fd): buf_ (fd) {} + +protected: + fdbuf buf_; +}; + +class ifdstream: fdstream_base, public std::istream +{ +public: + ifdstream (): std::istream (&buf_) {} + ifdstream (int fd): fdstream_base (fd), std::istream (&buf_) {} + + void close () {buf_.close ();} + void open (int fd) {buf_.open (fd);} + bool is_open () const {return buf_.is_open ();} +}; +)delim"); + + const char* r (R"delim( +CmNsYXNzIGZkc3RyZWFtX2Jhc2UKewpwcm90ZWN0ZWQ6CiAgZmRzdHJlYW1fYmFzZSAoKSA9IGRl +ZmF1bHQ7CiAgZmRzdHJlYW1fYmFzZSAoaW50IGZkKTogYnVmXyAoZmQpIHt9Cgpwcm90ZWN0ZWQ6 +CiAgZmRidWYgYnVmXzsKfTsKCmNsYXNzIGlmZHN0cmVhbTogZmRzdHJlYW1fYmFzZSwgcHVibGlj +IHN0ZDo6aXN0cmVhbQp7CnB1YmxpYzoKICBpZmRzdHJlYW0gKCk6IHN0ZDo6aXN0cmVhbSAoJmJ1 +Zl8pIHt9CiAgaWZkc3RyZWFtIChpbnQgZmQpOiBmZHN0cmVhbV9iYXNlIChmZCksIHN0ZDo6aXN0 +cmVhbSAoJmJ1Zl8pIHt9CgogIHZvaWQgY2xvc2UgKCkge2J1Zl8uY2xvc2UgKCk7fQogIHZvaWQg +b3BlbiAoaW50IGZkKSB7YnVmXy5vcGVuIChmZCk7fQogIGJvb2wgaXNfb3BlbiAoKSBjb25zdCB7 +cmV0dXJuIGJ1Zl8uaXNfb3BlbiAoKTt9Cn07Cg==)delim"); + + assert (encode (s, r + 1)); +} diff --git a/tests/buildfile b/tests/buildfile index 45c78d6..59caba8 100644 --- a/tests/buildfile +++ b/tests/buildfile @@ -2,6 +2,6 @@ # copyright : Copyright (c) 2014-2016 Code Synthesis Ltd # license : MIT; see accompanying LICENSE file -d = dir-iterator/ path/ prefix-map/ sha256/ timestamp/ triplet/ +d = base64/ dir-iterator/ path/ prefix-map/ sha256/ timestamp/ triplet/ .: $d include $d -- cgit v1.1