Indy10でメールを送信すると長い件名(Subject)が文字化けする問題と回避方法

Indy10でメールを送信すると、長い件名(Subject)が文字化けします。

サンプルコード

procedure TForm1.Button1Click(Sender: TObject);
var
  SMTP: TIdSMTP;
  Msg: TIdMessage;
begin
  SMTP := TIdSMTP.Create(nil);
  SMTP.Host := 'localhost';
  SMTP.Port := 25;

  Msg := TIdMessage.Create(SMTP);
  Msg.ContentType := 'text/plain';
  Msg.CharSet := 'ISO-2022-JP';
  Msg.ContentTransferEncoding := 'BASE64';
  Msg.From.Name := 'from@example.com';
  Msg.From.Address := 'from@example.com';
  Msg.Recipients.EMailAddresses := 'to@example.com';
  Msg.Subject := 'あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよわをん';
  Msg.Body.Text := '本文';
  Msg.OnInitializeISO := IdMessage_InitializeISO;

  SMTP.Connect;
  SMTP.Send(Msg);
  SMTP.Disconnect;

  Msg.Free;
  SMTP.Free;

  Close;
end;

procedure TForm1.IdMessage_InitializeISO(var VHeaderEncoding: Char;
  var VCharSet: string);
begin
  VHeaderEncoding := 'B';
  VCharSet := 'ISO-2022-JP';
end;

Outlook 2013では文字化けしません。

Outlook 2013

Becky! ver2では文字化けします。

Becky! ver2

これはデコード処理の違いによるものだと考えれます。
Indy10のメール送信処理においてBase64エンコードで文字化けが発生する問題について | 山本隆の開発日誌

問題のコードはIdCoderHeader.pasにありました。

EncodeWord関数の中に改行を追加する処理があります。

procedure EncodeWord(AP: Integer);
const
  MaxEncLen = 75;
  …
begin
  …
  if (EncLen + Length(Enc1)) > MaxEncLen then begin
    T := T + EndEncode + EOL + ' ' + BeginEncode;
    EncLen := Length(BeginEncode) + 2;
  end;

エンコードした文字列がMaxEncLenを超えるときに改行を追加しています。

とりあえず、この部分をコメントアウトすることで文字化けを回避することができまし
た。

C++Builder XEでMD5

昨日の「Delphi XEでMD5」をC++Builder XEで書いてみる。

#include <IdHashMessageDigest.hpp>

UnicodeString msg = "Hello, world";
TIdHashMessageDigest5* md5 = new TIdHashMessageDigest5();
UnicodeString hash = md5->HashStringAsHex(msg, TEncoding::ASCII).LowerCase();

ShowMessage(hash); //=> bc6e6f16b8a077ef5fbc8d59d0b931b9

Delphi XEでMD5

Team Japan » Indy 9 と Indy 10 でハッシュ値を計算 – MD5, SHA-1, SHA-256」では、TIdHashMessageDigest5のHashBytesAsHexメソッドにバイト配列を渡す方法が紹介されています。

uses IdHashMessageDigest;

procedure TForm1.Button1Click(Sender: TObject);
var
  md5: TIdHashMessageDigest5;
  msg: String;
  hash: String;
begin
  msg := 'Hello, world';
  md5 := TIdHashMessageDigest5.Create;
  hash := LowerCase(md5.HashBytesAsHex(TEncoding.ASCII.GetBytes(msg)));
  md5.Free;

  ShowMessage(hash); //=> bc6e6f16b8a077ef5fbc8d59d0b931b9
end;

もう一つの方法として、TIdHashMessageDigest5のHashStringAsHexメソッドに文字列とエンコーディングを渡す方法があります。

md5.HashStringAsHex(msg, TEncoding.ASCII)

procedure TForm1.Button2Click(Sender: TObject);
var
  md5: TIdHashMessageDigest5;
  msg: String;
  hash: String;
begin
  msg := 'Hello, world';
  md5 := TIdHashMessageDigest5.Create;
  hash := LowerCase(md5.HashStringAsHex(msg, TEncoding.ASCII));
  md5.Free;

  ShowMessage(hash); //=> bc6e6f16b8a077ef5fbc8d59d0b931b9
end;

タイプ数が少しだけ少ないです。

追記

Indy10でメールを送信する時TIdMessageはMessage-IDヘッダーを設定しない

Indy10でメールを送信する時、TIdMessageはMessage-IDヘッダーを設定しない。
Message-IDヘッダーを設定するコードを記述する必要がある。

//Message-IDヘッダーを設定する
IdMessage->ExtraHeaders->Values["Message-Id"] = Message-ID;

Message-ID関連でもう一つ。

TIdMessageはSaveToFileメソッドで保存する時はMessage-Idを保存するが、
SaveToStreamメソッドで保存する時はMessage-Idを保存しない。

//出力されたファイルにはMessage-IDがある
IdMessage1->SaveToFile("C:\\savetofile.eml");

//出力されたファイルにはMessage-IDがない
TFileStream* fs = new TFileStream("C:\\savetostream.eml", fmCreate);
IdMessage1->SaveToStream(fs);

C++Builder XEで確認しました。