空想犬猫記

※当日記では、犬も猫も空想も扱っておりません。(旧・エト記)

Base64 encoding/decoding filter

iostreamの拡張」を見てたら,自分でも何か書きたくなったので「Base64 - Wikipedia」を参考にしながら,Base64エンコード/デコードするC++のストリームフィルタを作ってみた。

使い方は簡単。base64_(en|de)code_filter を,(出力先|入力元)の stream で初期化して,あとは普通の stream だと思って使えば良い。例えばこんな感じ。

ostringstream oss;
{
  base64_encode_filter os(oss);
  os << "xoinu_de_gonsu.";
}

{
  string s;
  istringstream iss(oss.str(), ios::binary);
  base64_decode_filter is(iss);
  is >> s;
  cout << "Encoded = " << oss.str() << '\n'
       << "Decoded = " << s.c_str() << '\n';
}

出力結果:

Encoded = eG9pbnVfZGVfZ29uc3Uu
Decoded = xoinu_de_gonsu.

コードは以下の通り。BSDライセンスに基づいてご利用下さい。(例外処理はあまり考慮してませんのであしからず)

class base64_filter_buffer : public std::streambuf
{
public:
  base64_filter_buffer(std::ostream& os, bool mime = true) : os_(&os), is_(NULL), mime_(mime), cols_(0)
  {
    setp(&buffer_[0], &buffer_[2]);
  }

  base64_filter_buffer(std::istream& is) : os_(NULL), is_(&is), mime_(false), cols_(0)
  {}

  virtual ~base64_filter_buffer()
  {
    overflow(traits_type::eof());
  }

private:
  virtual int_type overflow(int_type c)
  {
    if (!os_)
      return traits_type::not_eof(c);

    char* end = pptr();

    if (!traits_type::eq_int_type(c, traits_type::eof()))
      *end++ = traits_type::to_char_type(c);

    char out[4];
    int length = end - buffer_;

    if (!length)
      return traits_type::eof();

    encode(buffer_, length, out);
    os_->write(out, 4);
    cols_ += 4;

    if (76 == cols_)
    {
      cols_ = 0;
      if (mime_)
        os_->write("\n", 1);
    }

    setp(&buffer_[0], &buffer_[2]);
    return traits_type::not_eof(c);
  }

  virtual int_type underflow()
  {
    if (!is_)
      return traits_type::eof();

    char ch, *it, *end;
    char chunk[4] = {'\0'};
    it  = &chunk[0];
    end = &chunk[4];

    while (is_->good() && it != end)
    {
      is_->read(&ch, 1);
      if ('\n' == ch || ' ' == ch || '\r' == ch)
        continue;
      if (!is_->good())
        break;
      *it++ = ch;
    }

    while (it != end)
      *it++ = '=';

    int length = 0;
    decode(chunk, buffer_, &length);
    setg(buffer_, buffer_, buffer_ + length);

    return length
      ? traits_type::not_eof(*buffer_)
      : traits_type::eof();
  }

private:
  void encode(const char* in, int length, char out[4])
  {
    static const char base64_table[65] =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

    switch (length)
    {
    case 1:
      out[0] = base64_table[in[0]>>2];
      out[1] = base64_table[(in[0]<<4)&63];
      out[2] = '=';
      out[3] = '=';
      break;
    case 2:
      out[0] = base64_table[in[0]>>2];
      out[1] = base64_table[((in[0]<<4)|(in[1]>>4))&63];
      out[2] = base64_table[(in[1]<<2)&63];
      out[3] = '=';
      break;
    case 3:
      out[0] = base64_table[(in[0]>>2)&63];
      out[1] = base64_table[((in[0]<<4)|(in[1]>>4))&63];
      out[2] = base64_table[((in[1]<<2)|(in[2]>>6))&63];
      out[3] = base64_table[in[2]&63];
      break;
    default:
      break;
    }
  }

  void decode(const char in[4], char* out, int* length)
  {
    *length = 0;
    int i;
    char n;

    for (i = 0; i < 4; ++i)
    {
      n = 0;

      if ('A' <= in[i] && in[i] <= 'Z') n = (in[i]-'A') ;
      else if ('a' <= in[i] && in[i] <= 'z') n = (in[i]-'a'+26);
      else if ('0' <= in[i] && in[i] <= '9') n = (in[i]-'0'+52);
      else if ('+' == in[i]) n = 62;
      else if ('/' == in[i]) n = 63;
      else if ('=' == in[i]) break;
      else throw std::runtime_error("Base64 encoding is invalid.");

      switch (i)
      {
      case 0:
        out[0]  = n << 2;
        *length = 1;
        break;
      case 1:
        out[0] |= (n >> 4);
        out[1]  = (n << 4);
        if (out[1])
          *length = 2;
        break;
      case 2:
        out[1] |= (n >> 2);
        out[2]  = (n << 6);
        if (out[2])
          *length = 3;
        break;
      case 3:
        out[2] |= n;
        *length = 3;
        break;
      default:
        break;
      }
    }
  }

private:
  std::ostream*   os_;
  std::istream*   is_;
  int             cols_;
  bool            mime_;
  char            buffer_[4];
};

class base64_encode_filter : public std::ostream
{
public:
  explicit base64_encode_filter(std::ostream& os)
    : std::ostream(&buf_), buf_(os)
  {}
private:
  base64_filter_buffer buf_;
};

class base64_decode_filter : public std::istream
{
public:
  explicit base64_decode_filter(std::istream& is)
    : std::istream(&buf_), buf_(is)
  {}
private:
  base64_filter_buffer buf_;
};