flight 001: iphone mail 文字コード判別


.
よく「iphone mail で魔法の文字を入れて送信すれば文字化けを回避できる」
という記事を見かけるとおり、
送信側の文字コードが、通例では

旧来のメーラーは、jis ( iso-2022-jp )
昨今のメーラーは、utf8

と、ほぼ固定(一部例外有)だったけど

iphone mail は
文字コード可変送信型メーラー

に、なっている

sendmail するにしても、web に渡すにしても
メールパースする場合、なんとも厄介この上ない

ひとまずやってみたので一例

[ 食感 (要点) ]

  1. 件名 mime encode 値 と 本文 Jcode::getcode 値 を用いて判定
  2. 上記 2つ ( 件名 と 本文 ) の判定違いも注意
  3. getcode の特性上、判定できないものもある
  4. 他国語も もれなく 注意必要

[ お品書き ]

1. キッチン ( 使用環境、検証環境 )
2. 食材
3. 仕込
4. 調理 1 - 通常ケース
5. 調理 2 - 特殊 1 - ハートマーク
6. 調理 3 - 特殊 2 - 波ダッシュ、他
7. 調理 4 - イレギュラー 1 - 中国語
8. 調理 5 - イレギュラー 2 - 判定不能
9. デザート ( まとめ、あと書き )

[ 1. キッチン ( 使用環境、検証環境 ) ]

xserver x10
perl 5.16.3

iphone SE (iOS 9.3.5)
iPhone Mail (13G36)

[ 2. 食材 ]

  1. mime encode された (件名 $subject)
    例: =?UTF-8?Q?

  2. Jcode::getcode (本文 $body) の値
    例: utf8

[ 3. 仕込 ]

パース後、こんな感じで、件名 ( subject ) と 本文 ( body ) を入れておく


use Jcode;
use Encode;
use MIME::Parser;

# =============================
# 件名 

my $subject = $header->get('Subject');
chomp($subject);

$sub01 = $subject;
$sub02 = substr($sub01, 2, 3); # 3文字抜き
$sub03 = substr($sub01, 6, 2); # EUC用にハイフン後2文字抜き
$sub04 = substr($sub01, 2, 6); # 5文字抜き

# 半角 (1バイト) のみチェック ( = 1 なら半角のみ )
# ( 本来 件名に 2バイトが入ることはないが念の為 )
$sub05 = 0;
if ( $sub01 =~ /^[\x20-\x7E]+$/ ) {
    $sub05 = 1;
}

# 件名なしチェック
$sub06 = 0;
if ( $sub01 eq '' ) {
    $sub06 = 1;
}

# =============================
# 本文 ( multipart 無しだったとして ) 

$body = $entity->bodyhandle->as_string;
chomp($body);

$bo01 = Jcode::getcode($body);

# =============================

[ 4. 調理 1 - 通常ケース ]

(1) 件名、本文 ともに 日本語

sub01: =?iso-2022-jp?B?GyRCJCIbKEI=?=
sub02: iso
sub03: 20
sub04: iso-20
sub05: 1
sub06: 0
body(bo01): jis

-> 件名 iso-2022-jp, 本文 jis のシンプル

(2) 件名(例: aaa)、本文 ともに 英語(英数半角/1バイト)

sub01: aaa
sub02: a
sub03:
sub04: a
sub05: 1
sub06: 0
body(bo01): ascii

-> 件名 mime encode 無し(ascii), 本文 ascii これもシンプル

[ 5. 調理 2 - 特殊 1 ]

(1) 件名、本文 ともに ハート( ♡ )

sub01: =?euc-kr?Q?=A2=BD_su?=
sub02: euc
sub03: kr
sub04: euc-kr
sub05: 1
sub06: 0
body(bo01): euc

-> 件名 euc-kr, 本文 euc

euc-kr ( 韓国系 euc ) 初めて見ました
これいわゆる utf8 固定になると言われている文字列で、
署名に入れると utf8 になる ? ( getcode では utf8 にならないのか未検証 )

[ 6. 調理 3 - 特殊 2 ]

(1) 件名、本文 ともに 0x8160 ( sjis ):
通称 波ダッシュ

sub01: =?cp932?Q?150=81`30?=
sub02: cp9
sub03: 2?
sub04: cp932?
sub05: 1
sub06: 0
body(bo01): sjis

-> 件名 cp932, 本文 sjis

メーラーで cp932 も初めて見ました
波ダッシュは、web ページから貼り付けて送信すると入ってきます

(2) 件名、本文 ともに 0x8166 ( sjis ):
2バイトアポストロフィー ? ( 正式名分かってません )

sub01: =?cp932?Q?You=81fve?=
sub02: cp9
sub03: 2?
sub04: cp932?
sub05: 1
sub06: 0
body(bo01): sjis

-> 件名 cp932, 本文 sjis

2バイトアポストロフィー は、海外からのメールなんかでたまに入ってきて、
iso-2022-jp や utf8 で何も考えず転送送信すると文字化けてます

しょうがないので unpack して 1バイトアポストロフィーに変換します


$bod01 = $body;
$bod01 =~ s/^\s*(.*?)\s*$/$1/;
$bod02 = uc unpack("H*", $bod01);

$bod02 =~ s/8166/27/mg;

$bod03 = pack("H*", $bod02);

[ 7. 調理 4 - イレギュラー 1 ]

(1) 件名、本文 ともに 中国語

sub01: =?GB2312?Q?60_=D5=E2?=
sub02: GB2
sub03: 12
sub04: GB2312
sub05: 1
sub06: 0
body(bo01): euc

-> 件名 GB2312, 本文 euc

subject と body の判定が一致しない ...

Jcode::getcode では 以下の値が取得できるらしいです
binary / ascii / euc / sjis / jis / ucs2 / utf8 / undef

euc の場合、
euc-jp (日本系) / euc-cn (中国系) / euc-tw (台湾系) / euc-kr (韓国系)
など、いろいろあるので、このケースは、euc-cn と推測

でも、件名の GB2312 から本文も同じく以下にしたら文字化けなく OK

Encode::from_to($body, 'GB2312', 'utf8');

ただし、簡体字 / 繁体字 などの違いで、Big5 や GB18030 などに変わるのかも

[ 8. 調理 5 - イレギュラー 2 ]

件名 が 空 で、本文 の getcode が undef など判定不能 の場合は、もうどうしようもない
実際、getcode で、binary / ucs2 / undef あたりになると、どうしたものか

でも、テストケースも思いつかないので unknown 判定で、保留

[ 9. デザート ( あと書き ) ]

もちろん件名と本文が、同一文字コードではないケースも普通の話なので、
同一の判定をすることはできないけど
getcode 判定は 時として 曖昧なので、mime encode も用いて総合判断する

※以下とても注意!
(1) perl の version
(2) Jcode の version
(3) iphone の 機種
(4) iOS の version
(5) iphone mail の version
によって判定値 は 変わる可能性があるので注意が必要
(あくまでも参考程度に。最新iOSでは変わっているかも)

今後も apple 様の思うままに仕様変更はありそう

.