AUの件名が文字化けしたのはNet::Cmdの仕業だったでござるの巻

既知の事実かもしれないがハマったのでメモメモ。

こーんなシンプルなメール送信プログラムを書いたのです。

use strict;
use warnings;
use MIME::Lite;
use Path::Class;

my $mime = MIME::Lite->new(
    From          => 'from@exmaple.com',
    To            => 'xxxxxxxxxxxxx@ezweb.ne.jp',
    Subject       => subject() || '',
    Data          => body() || '',
    Encoding      => 'base64',
);
$mime->attr( 'content-type.charset' => 'Shift_JIS' );
$mime->attr( "Content-disposition"  => "" );
$mime->field_order(
    'MIME-Version', 'Content-Type', 'Content-Transfer-Encoding'
);

$mime->send('smtp','exmaplehost' );

sub subject {
    return file('./data/subject.txt')->slurp;
}

sub body {
    return file('./data/message.txt')->slurp;
}

件名と本文はファイルから読み出してますが、要はShift_JISの文字列になります。

すると件名がぐちゃぐちゃっと文字化けになるわけなのです。

sendmailだったら問題なかったのにSMTPにしたら問題になったのでNet::SMTPが悪さしてるに違いない!

で、

$mime->send('smtp','exmaplehost' , Debug => 1 );

ってな感じでデバッグモードで送信してみると、DATAコマンドが送出される時点でもう既に文字化けしてるのがわかりました。

そんな感じで調査していくうちに衝撃の事実にブチ当たったわけです。

ズバリNet::SMTPの親クラスのNet::Cmdがえらいことになってました。抜粋すると・・・。

# Net::Cmd-v2.27

my $doUTF8 = eval { require utf8 };


〜中略〜


sub datasend
{
 my $cmd = shift;
 my $arr = @_ == 1 && ref($_[0]) ? $_[0] : \@_;
 my $line = join("" ,@$arr);
 
 utf8::encode($line) if $doUTF8;


〜省略〜

ってな感じでdatasendメソッドの中でdoUTF8が真なら問答無用でutf8::encodeを呼んじゃってるわけなのです。

ちなみにこのdatasendは件名をセットする時にも呼ばれるのでShift_JISの文字列がutf8::encodeされちゃってぐっちゃりってなっちゃうわけだったのです。

で、最新のNet::Cmd-v2.29とdiffしてみたところ

-my $doUTF8 = eval { require utf8 };
+BEGIN {
+  if (!eval { require utf8 }) {
+    *is_utf8 = sub { 0 };
+  }
+  elsif (eval { utf8::is_utf8(undef); 1 }) {
+    *is_utf8 = \&utf8::is_utf8;
+  }
+  elsif (eval { require Encode; Encode::is_utf8(undef); 1 }) {
+    *is_utf8 = \&Encode::is_utf8;
+  }
+  else {
+    *is_utf8 = sub { $_[0] =~ /[^\x00-\xff]/ };
+  }
+}


〜中略〜


-sub datasend
-{
- my $cmd = shift;
- my $arr = @_ == 1 && ref($_[0]) ? $_[0] : \@_;
- my $line = join("" ,@$arr);
 
- utf8::encode($line) if $doUTF8;
+sub datasend {
+  my $cmd  = shift;
+  my $arr  = @_ == 1 && ref($_[0]) ? $_[0] : \@_;
+  my $line = join("", @$arr);
 
- return 0 unless defined(fileno($cmd));
+  # encode to individual utf8 bytes if
+  # $line is a string (in internal UTF-8)
+  utf8::encode($line) if is_utf8($line);
http://search.cpan.org/diff?from=libnet-1.20&to=libnet-1.22#Net/Cmd.pm

となっていて、一安心といったところでしょうか。# ちゅーか何がしたいんだこのis_utf8はw

いやーでもどこで躓くかわかんないね。っんとにもう。

ちなみにMIMEエンコードしたりJISとかにしたりすればええやん!ってのは言いっこなしね。絵文字使いたかったの。


調査を手伝って頂いた某氏に謝謝!