C#でのquoted-printableのエンコード/デコード処理の実装。

以下のQuotedPrintableクラスでは、エンコード・デコードを行うメソッドとしてヘッダ用のものとボディ用のものを用意しているため、目的に応じて使い分けることができる。

EncodeHeaderメソッド
文字列をヘッダ用にエンコード(Qエンコード)する。 空白・水平タブに加えてエクスクラメーションマーク?とアンダースコア_もクオートするようにしている。
EncodeBodyメソッド
文字列をボディ用にエンコードする。 自動的にソフト改行する(各行を76文字以内に納める)ようにしてある。
DecodeHeaderメソッド
文字列をヘッダ内の文字列(Qエンコードされた文字列)としてデコードする。 アンダースコア_は空白に変換する。
DecodeBodyメソッド
文字列をボディ内の文字列としてデコードする。 アンダースコア_は変換せず、そのままアンダースコア_として解釈する。

各メソッドは任意の文字コード(Encodingクラス)を指定できるようになっている。

quoted-printableのエンコード/デコードを行うクラス
using System;
using System.IO;
using System.Text;

static class QuotedPrintable {
  // 16進文字への変換テーブル
  private static readonly byte[] hexChars = new byte[] {
    0x30, 0x31, 0x32, 0x33, // '0', '1', '2', '3'
    0x34, 0x35, 0x36, 0x37, // '4', '5', '6', '7'
    0x38, 0x39, 0x41, 0x42, // '8', '9', 'A', 'B'
    0x43, 0x44, 0x45, 0x46, // 'C', 'D', 'E', 'F'
  };

  // エンコード
  public static string EncodeBody(string quoteString, Encoding encoding)
  {
    if (encoding == null)
      throw new ArgumentNullException("encoding");

    return Encode(encoding.GetBytes(quoteString), false);
  }

  public static string EncodeHeader(string quoteString, Encoding encoding)
  {
    if (encoding == null)
      throw new ArgumentNullException("encoding");

    return Encode(encoding.GetBytes(quoteString), true);
  }

  private static string Encode(byte[] quoteBytes, bool quoteHeader)
  {
    if (quoteBytes == null)
      throw new ArgumentNullException("quoteBytes");

    // とりあえず変換前の4倍の容量でMemoryStreamを作成
    using (var quoted = new MemoryStream(quoteBytes.Length * 4)) {
      var charcount = 0;

      foreach (var octet in quoteBytes) {
        if (!quoteHeader && 73 < charcount) {
          // 次のエスケープで76文字を越える可能性がある場合、ソフト改行を入れる
          var escaped = false;

          quoted.WriteByte(0x3d); // '=' 0x3d

          if (octet == 0x09 || octet == 0x20) {
            // '\t' 0x09 or ' ' 0x20
            quoted.WriteByte(hexChars[(octet & 0xf0) >> 4]);
            quoted.WriteByte(hexChars[octet & 0x0f]);

            escaped = true;
          }

          quoted.WriteByte(0x0d); // '\r' 0x0d
          quoted.WriteByte(0x0a); // '\n' 0x0a

          charcount = 0;

          if (escaped)
            continue;
        }

        var quote = false;

        switch (octet) {
          case 0x09: // horizontal tab
          case 0x20: // space
          case 0x3f: // '?'
          case 0x5f: // '_'
            quote = quoteHeader;
            break;

          case 0x3d: // '='
            quote = true;
            break;

          default:
            // quote non-printable chars
            quote = (octet < 0x21 || 0x7f < octet);
            break;
        }

        if (quote) {
          // '=' 0x3d or non printable char
          quoted.WriteByte(0x3d); // '=' 0x3d
          quoted.WriteByte(hexChars[(octet & 0xf0) >> 4]);
          quoted.WriteByte(hexChars[octet & 0x0f]);

          charcount += 3;
        }
        else {
          // printable char (except '=' 0x3d)
          quoted.WriteByte(octet);

          charcount++;
        }
      } // foreach

      return Encoding.ASCII.GetString(quoted.ToArray());
    } // using
  }

  // デコード
  public static string DecodeBody(string quotedString, Encoding encoding)
  {
    if (encoding == null)
      throw new ArgumentNullException("encoding");

    return encoding.GetString(Decode(quotedString, false));
  }

  public static string DecodeHeader(string quotedString, Encoding encoding)
  {
    if (encoding == null)
      throw new ArgumentNullException("encoding");

    return encoding.GetString(Decode(quotedString, true));
  }

  private static byte[] Decode(string quotedString, bool dequoteHeader)
  {
    if (quotedString == null)
      throw new ArgumentNullException("quotedString");

    using (var decoded = new MemoryStream(quotedString.Length)) {
      var buffer = new byte[3];
      var bufferOffset = 0;

      foreach (byte octet in quotedString.ToCharArray()) {
        if (bufferOffset == 0) {
          if (octet == 0x3d) // '=' 0x3d
            // quoted
            buffer[bufferOffset++] = octet;
          else if (dequoteHeader && octet == 0x5f) // '_' 0x5f
            decoded.WriteByte(0x20); // ' ' 0x20
          else
            // non-quoted
            decoded.WriteByte(octet);
        }
        else {
          // quoted char
          buffer[bufferOffset++] = octet;
        }

        if (bufferOffset == 3) {
          // dequote
          if (buffer[1] == 0x0d && buffer[2] == 0x0a) {
            // soft newline ('\r' 0x0d + '\n' 0x0a)
            bufferOffset = 0;
          }
          else if (buffer[1] == 0x0d || buffer[1] == 0x0a) {
            // soft newline ('\r' 0x0d or '\n' 0x0a)
            if (buffer[2] == 0x3d) {
              bufferOffset = 1;
            }
            else {
              decoded.WriteByte(buffer[2]);
              bufferOffset = 0;
            }
          }
          else {
            byte d = 0x00;

            for (var i = 1; i < 3; i++) {
              d <<= 4;

              if (0x30 <= buffer[i] && buffer[i] <= 0x39)
                // '0' 0x30 to '9' 0x39
                d |= (byte)(buffer[i] - 0x30);
              else if (0x41 <= buffer[i] && buffer[i] <= 0x46)
                // 'A' 0x41 to 'F' 0x46
                d |= (byte)(buffer[i] - 0x37);
              else if (0x61 <= buffer[i] && buffer[i] <= 0x66)
                // 'a' 0x61 to 'f' 0x66
                d |= (byte)(buffer[i] - 0x57);
              else
                throw new FormatException("incorrect form");
            }

            decoded.WriteByte(d);

            bufferOffset = 0;
          }
        }
      } // foreach

      return decoded.ToArray();
    } // using
  }
}

以下、使用例と実行結果。

実行結果
[encode/decode]
text = '漢字abc かな?123カナ'
EncodeBody:   =1B$B4A;z=1B(Babc =1B$B$+$J=1B(B?123=1B$B%+%J=1B(B
EncodeHeader: =1B$B4A;z=1B(Babc=20=1B$B$+$J=1B(B=3F123=1B$B%+%J=1B(B

text = '=1B$B4A;z=1B(Babc_=1B$B$+$J=1B(B=3F123=1B$B%+%J=1B(B'
DecodeBody:   漢字abc_かな?123カナ
DecodeHeader: 漢字abc かな?123カナ

text = '=1B=24B4A=3Bz=1B=28Babc_=1B=24B=24=2B=24J=1B=28B=3F123=1B=24B=25=2B=25J5'
DecodeBody:   漢字abc_かな?123カナ
DecodeHader:  漢字abc かな?123カナ

[decode soft newline]
Now's the time for all folk to come to the aid of their country.
Now's the time for all folk to come to the aid of their country.

[encode soft newline]
The quick brown fox jumps over the lazy dog=0AThe quick brown fox jumps ov=
er the lazy dog=0AThe quick brown fox jumps over the lazy dog=0AThe quick =
brown fox jumps over the lazy dog=0AThe quick brown fox jumps over the laz=
y dog=0AThe quick brown fox jumps over the lazy dog

The=20quick=20brown=20fox=20jumps=20over=20the=20lazy=20dog=0AThe=20quick=20brown=20fox=20jumps=20over=20the=20lazy=20dog=0AThe=20quick=20brown=20fox=20jumps=20over=20the=20lazy=20dog=0AThe=20quick=20brown=20fox=20jumps=20over=20the=20lazy=20dog=0AThe=20quick=20brown=20fox=20jumps=20over=20the=20lazy=20dog=0AThe=20quick=20brown=20fox=20jumps=20over=20the=20lazy=20dog
using System;
using System.Text;

class Sample {
  static void Main(string[] args)
  {
    var iso2022jp = Encoding.GetEncoding("iso-2022-jp");

    Console.WriteLine("[encode/decode]");

    var text = "漢字abc かな?123カナ";
    Console.WriteLine("text = '{0}'", text);
    Console.WriteLine("EncodeBody:   {0}", QuotedPrintable.EncodeBody  (text, iso2022jp));
    Console.WriteLine("EncodeHeader: {0}", QuotedPrintable.EncodeHeader(text, iso2022jp));
    Console.WriteLine();

    text = "=1B$B4A;z=1B(Babc_=1B$B$+$J=1B(B=3F123=1B$B%+%J=1B(B";
    Console.WriteLine("text = '{0}'", text);
    Console.WriteLine("DecodeBody:   {0}", QuotedPrintable.DecodeBody  (text, iso2022jp));
    Console.WriteLine("DecodeHeader: {0}", QuotedPrintable.DecodeHeader(text, iso2022jp));
    Console.WriteLine();

    text = "=1B=24B4A=3Bz=1B=28Babc_=1B=24B=24=2B=24J=1B=28B=3F123=1B=24B=25=2B=25J5";
    Console.WriteLine("text = '{0}'", text);
    Console.WriteLine("DecodeBody:   {0}", QuotedPrintable.DecodeBody  (text, iso2022jp));
    Console.WriteLine("DecodeHader:  {0}", QuotedPrintable.DecodeHeader(text, iso2022jp));
    Console.WriteLine();

    Console.WriteLine("[decode soft newline]");
    Console.WriteLine(QuotedPrintable.DecodeBody("Now's the time =\r\nfor all folk to come=\r\n to the aid of their country.", Encoding.ASCII));
    Console.WriteLine(QuotedPrintable.DecodeBody("Now's the=\n time =\rfor all folk to come =\r\nto the aid=\r=\n of their country.", Encoding.ASCII));
    Console.WriteLine();

    Console.WriteLine("[encode soft newline]");
    Console.WriteLine(QuotedPrintable.EncodeBody(@"The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog", Encoding.ASCII));
    Console.WriteLine();
    Console.WriteLine(QuotedPrintable.EncodeHeader(@"The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog", Encoding.ASCII));
  }
}