aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS2
-rw-r--r--butl/base6448
-rw-r--r--butl/base64.cxx211
-rw-r--r--butl/buildfile1
-rw-r--r--tests/base64/buildfile7
-rw-r--r--tests/base64/driver.cxx106
-rw-r--r--tests/buildfile2
7 files changed, 376 insertions, 1 deletions
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 <iosfwd>
+#include <string>
+#include <vector>
+
+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<char>&);
+
+ // 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<char>
+ 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 <butl/base64>
+
+#include <cstddef> // size_t
+#include <istream>
+#include <ostream>
+#include <iterator> // {istreambuf, ostreambuf, back_insert}_iterator
+#include <stdexcept> // 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 <typename I, typename O>
+ 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 <typename I, typename O>
+ 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<char> i (is);
+ back_insert_iterator<string> o (r);
+
+ base64_encode (i, istreambuf_iterator<char> (), 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<char> i (is);
+ ostreambuf_iterator<char> o (os);
+ base64_encode (i, istreambuf_iterator<char> (), o);
+
+ if (o.failed ())
+ os.setstate (istream::badbit);
+
+ is.setstate (istream::eofbit);
+ }
+
+ std::string
+ base64_encode (const std::vector<char>& v)
+ {
+ string r;
+ back_insert_iterator<string> 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<char> i (is);
+ ostreambuf_iterator<char> o (os);
+ base64_decode (i, istreambuf_iterator<char> (), 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<char> o (os);
+ auto i (s.cbegin ());
+ base64_decode (i, s.cend (), o);
+
+ if (o.failed ())
+ os.setstate (istream::badbit);
+ }
+
+ vector<char>
+ base64_decode (const string& s)
+ {
+ vector<char> r;
+ back_insert_iterator<vector<char>> 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 <string>
+#include <sstream>
+#include <cassert>
+
+#include <butl/base64>
+
+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<char> (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<char> 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