Browse Source

base64 encoder and decoder using boost base64_from_binary and
binary_from_base64


git-svn-id: svn://bind10.isc.org/svn/bind10/branches/parkinglot@727 e5f2f494-b856-4b98-b285-d166d9295462

JINMEI Tatuya 15 years ago
parent
commit
c635b9ac75
4 changed files with 337 additions and 0 deletions
  1. 2 0
      src/lib/dns/cpp/Makefile.am
  2. 188 0
      src/lib/dns/cpp/base64.cc
  3. 53 0
      src/lib/dns/cpp/base64.h
  4. 94 0
      src/lib/dns/cpp/base64_unittest.cc

+ 2 - 0
src/lib/dns/cpp/Makefile.am

@@ -15,6 +15,7 @@ libdns_la_SOURCES += rdataclass.h rdataclass.cc
 libdns_la_SOURCES += rrset.h rrset.cc
 libdns_la_SOURCES += question.h question.cc
 libdns_la_SOURCES += message.h message.cc
+libdns_la_SOURCES += base64.h base64.cc
 libdns_la_SOURCES += exceptions.h exceptions.cc
 
 rrclass.h: rrclass-placeholder.h
@@ -36,6 +37,7 @@ run_unittests_SOURCES += rrset_unittest.cc
 run_unittests_SOURCES += question_unittest.cc
 run_unittests_SOURCES += rrparamregistry_unittest.cc
 run_unittests_SOURCES += message_unittest.cc
+run_unittests_SOURCES += base64_unittest.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_CPPFLAGS = $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(GTEST_LDFLAGS)

+ 188 - 0
src/lib/dns/cpp/base64.cc

@@ -0,0 +1,188 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// 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 ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC 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.
+
+// $Id$
+
+#include <cassert>
+#include <iterator>
+#include <string>
+#include <vector>
+
+#include <boost/archive/iterators/base64_from_binary.hpp>
+#include <boost/archive/iterators/binary_from_base64.hpp>
+#include <boost/archive/iterators/transform_width.hpp>
+
+#include "base64.h"
+#include "exceptions.h"
+
+using namespace std;
+using namespace boost::archive::iterators;
+
+namespace isc {
+namespace dns {
+
+namespace {
+const char BINARY_ZERO_CODE = 0;
+
+typedef
+class BinaryNormalizer : public iterator<input_iterator_tag, char> {
+public:
+    BinaryNormalizer(const vector<char>::const_iterator& base,
+                     const vector<char>::const_iterator& base_end) :
+        base_(base), base_end_(base_end), in_pad_(false)
+    {}
+    BinaryNormalizer& operator++()
+    {
+        if (!in_pad_) {
+            ++base_;
+        }
+        if (base_ == base_end_) {
+            in_pad_ = true;
+        }
+        return (*this);
+    }
+    const char& operator*() const {
+        if (in_pad_) {
+            return (BINARY_ZERO_CODE);
+        } else {
+            return (*base_);
+        }
+    }
+    bool operator==(const BinaryNormalizer& other) const
+    {
+        return (base_ == other.base_);
+    }
+private:
+    vector<char>::const_iterator base_;
+    const vector<char>::const_iterator base_end_;
+    bool in_pad_;
+};
+
+typedef 
+base64_from_binary<transform_width<BinaryNormalizer, 6, 8> > base64_encoder;
+} // end of anonymous namespace
+
+string
+encodeBase64(const vector<char>& binary)
+{
+    // calculate the resulting length.  it's the smallest multiple of 4
+    // equal to or larger than 4/3 * original data length.
+    size_t len = ((binary.size() * 4 / 3) + 3) & ~3;
+
+    string base64;
+    base64.reserve(len);
+    base64.assign(base64_encoder(BinaryNormalizer(binary.begin(),
+                                                  binary.end())),
+                  base64_encoder(BinaryNormalizer(binary.end(), binary.end())));
+    assert(len >= base64.length());
+    base64.append(len - base64.length(), '=');
+    return (base64);
+}
+
+namespace {
+const char BASE64_PADDING_CHAR = '=';
+const size_t BASE64_MAX_PADDING_CHARS = 2;
+const char BASE64_ZERO_CODE = 'A'; // correspond to 000000(2)
+
+class Base64Normalizer : public iterator<input_iterator_tag, char> {
+public:
+    Base64Normalizer(const string::const_iterator& base,
+                     const string::const_iterator& base_beginpad,
+                     const string::const_iterator& base_end) :
+        base_(base), base_beginpad_(base_beginpad), base_end_(base_end),
+        in_pad_(false)
+    {}
+    Base64Normalizer& operator++()
+    {
+        ++base_;
+        while (base_ != base_end_ && isspace(*base_)) {
+            ++base_;
+        }
+        if (base_ == base_beginpad_) {
+            in_pad_ = true;
+        }
+        return (*this);
+    }
+    const char operator*() const {
+        if (in_pad_ && *base_ == BASE64_PADDING_CHAR) {
+            return (BASE64_ZERO_CODE);
+        } else {
+            return (*base_);
+        }
+    }
+    bool operator==(const Base64Normalizer& other) const
+    {
+        return (base_ == other.base_);
+    }
+private:
+    string::const_iterator base_;
+    const string::const_iterator base_beginpad_;
+    const string::const_iterator base_end_;
+    bool in_pad_;
+};
+
+typedef
+transform_width<binary_from_base64<Base64Normalizer, char>, 8, 6, char>
+base64_decoder;
+} // end of anonymous namespace
+
+void
+decodeBase64(const string& base64, vector<char>& result)
+{
+    // enumerate the number of trailing padding characters (=), ignoring
+    // white spaces.  since base64_from_binary doesn't accept padding,
+    // we handle it explicitly.
+    size_t padlen = 0;
+    string::const_reverse_iterator srit = base64.rbegin();
+    string::const_reverse_iterator srit_end = base64.rend();
+    while (srit != srit_end) {
+        char ch = *srit;
+        if (ch == BASE64_PADDING_CHAR) {
+            if (++padlen > BASE64_MAX_PADDING_CHARS) {
+                dns_throw(BadBase64String,
+                          "Too many Base64 padding characters");
+            }
+        } else if (!isspace(ch)) {
+            break;
+        }
+        ++srit;
+    }
+
+    try {
+        result.assign(base64_decoder(Base64Normalizer(base64.begin(),
+                                                      srit.base(),
+                                                      base64.end())),
+                      base64_decoder(Base64Normalizer(base64.end(),
+                                                      base64.end(),
+                                                      base64.end())));
+    } catch (dataflow_exception& ex) {
+        dns_throw(BadBase64String, ex.what());
+    }
+
+    // Confirm the original base64 text is the canonical encoding of the
+    // data.
+    assert(result.size() >= padlen);
+    vector<char>::const_reverse_iterator rit = result.rbegin();
+    for (int i = 0; i < padlen; ++i, ++rit) {
+        if (*rit != 0) {
+            dns_throw(BadBase64String, "Non 0 bits included in padding");
+        }
+    }
+
+    // strip the padded zero-bit fields
+    result.resize(result.size() - padlen);
+}
+
+}
+}

+ 53 - 0
src/lib/dns/cpp/base64.h

@@ -0,0 +1,53 @@
+// Copyright (C) 2009  Internet Systems Consortium, Inc. ("ISC")
+//
+// 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 ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC 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.
+
+// $Id$
+
+#ifndef __BASE64_H
+#define __BASE64_H 1
+
+#include <string>
+#include <vector>
+
+#include "exceptions.h"
+
+//
+// Note: this helper module isn't specific to the DNS protocol per se.
+// We should probably move this to somewhere else, possibly in some common
+// utility area.
+//
+
+namespace isc {
+namespace dns {
+
+///
+/// \brief A standard DNS (or ISC) module exception that is thrown a Base64
+/// decoder encounters an invalid input.
+///
+class BadBase64String : public Exception {
+public:
+    BadBase64String(const char* file, size_t line, const char* what) :
+        isc::dns::Exception(file, line, what) {}
+};
+
+std::string encodeBase64(const std::vector<char>& binary);
+void decodeBase64(const std::string& base64, std::vector<char>& result);
+}
+}
+
+#endif  // __BASE64_H
+
+// Local Variables: 
+// mode: c++
+// End: 

+ 94 - 0
src/lib/dns/cpp/base64_unittest.cc

@@ -0,0 +1,94 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// 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 ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC 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.
+
+// $Id$
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base64.h"
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::dns;
+
+namespace {
+
+typedef pair<string, string> StringPair;
+
+class Base64Test : public ::testing::Test {
+protected:
+    Base64Test()
+    {
+        // test vectors from RFC4648
+        test_sequence.push_back(StringPair("", ""));
+        test_sequence.push_back(StringPair("f", "Zg=="));
+        test_sequence.push_back(StringPair("fo", "Zm8="));
+        test_sequence.push_back(StringPair("foo", "Zm9v"));
+        test_sequence.push_back(StringPair("foob", "Zm9vYg=="));
+        test_sequence.push_back(StringPair("fooba", "Zm9vYmE="));
+        test_sequence.push_back(StringPair("foobar", "Zm9vYmFy"));
+    }
+    vector<StringPair> test_sequence;
+    vector<char> decoded_data;
+};
+
+void
+decodeCheck(const string& input_string, vector<char>& output,
+            const string& expected)
+{
+    decodeBase64(input_string, output);
+    EXPECT_EQ(expected, string(&output[0], &output[0] + output.size()));
+}
+
+TEST_F(Base64Test, decode)
+{
+    for (vector<StringPair>::const_iterator it = test_sequence.begin();
+         it != test_sequence.end();
+         ++it) {
+        decodeCheck((*it).second, decoded_data, (*it).first);
+    }
+
+    // whitespace should be allowed
+    decodeCheck("Zm 9v\tYmF\ny", decoded_data, "foobar");
+    decodeCheck("Zm9vYg==", decoded_data, "foob");
+    decodeCheck("Zm9vYmE=\n", decoded_data, "fooba");
+
+    // only up to 2 padding characters are allowed
+    EXPECT_THROW(decodeBase64("A===", decoded_data), BadBase64String);
+    EXPECT_THROW(decodeBase64("A= ==", decoded_data), BadBase64String);
+
+    // intermediate padding isn't allowed
+    EXPECT_THROW(decodeBase64("YmE=YmE=", decoded_data), BadBase64String);
+
+    // Non canonical form isn't allowed.
+    // Z => 25(011001), m => 38(100110), 9 => 60(111101), so the padding
+    // byte would be 0100 0000.
+    EXPECT_THROW(decodeBase64("Zm9=", decoded_data), BadBase64String);
+    // Same for the 1st padding byte.  This would make it 01100000.
+    EXPECT_THROW(decodeBase64("Zm==", decoded_data), BadBase64String);
+}
+
+TEST_F(Base64Test, encode)
+{
+    for (vector<StringPair>::const_iterator it = test_sequence.begin();
+         it != test_sequence.end();
+         ++it) {
+        decoded_data.assign((*it).first.begin(), (*it).first.end());
+        EXPECT_EQ((*it).second, encodeBase64(decoded_data));
+    }
+}
+}