WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

長い日本語の添付ファイル名が文字化け

by WebSurfer 2011年10月30日 22:00

.NET Framework 4 の SmtpClient を使用すると、添付ファイルに長い日本語のファイル名を使うと文字化けするという問題があります。(.NET 3.5 以前では問題なし)

原因はファイル名が二重エンコーディングされてしまうからで、そのメカニズムは このブログ に詳しく書いてあります。

この問題は、米国の MSDN フォーラムでも報告されており、原因が .NET Framework 4 のバグであることは Microsoft も認識しているようです。

その時の Microsoft の人の回答(2010/7/23 付けの記事参照)では、次のリリースを待つか、短い名前にするしかないということでした。

とは言え、現実に問題ない PC があるのは確かですので(自分の開発マシンがそうです)、何らかの更新プログラムがリリースされていて、問題が発生しない PC には適用されているはずと思って調べたら、Connect のページ に、Hotfix KB2183292 によってファイル名の二重にエンコーディングの問題も解決されるとの話がありました。

試しに、二重エンコーディングの問題の出ていた PC に、Hotfix KB2183292 をダウンロードしてインストールすると問題が解決しました。

Windows Update でインストールされる .NET Framework 4 用の更新プログラム で Hotfix KB2183292 が置き換えられると書いてありましたので、この更新プログラムによって二重エンコーディングの問題も解決されているのかと思いましたが、どうもそうではないようです。

実際、.NET Framework 4 用の更新プログラム がインストールされている PC でも二重エンコーディングの問題が発生しました。

というわけで、Windows Update で解決されているのではなさそうなので、次期リリース(.NET 4.5 ?)でこの問題が解決されるまでは、長い日本語のファイル名を添付ファイルに使う場合は、何らかの対策が必要なようです。

問題は、Attachment コンストラクタ (String, ContentType) で長い日本語のファイル名を使用すると二重エンコーディングされるということなので、代わりに Attachment コンストラクタ (Stream, ContentType) を使用し、Content-Disposition の filename の方にファイル名を設定することで問題を回避できるはずです。

以下のような感じです。

private string EncodeHeader(string str, Encoding enc)
{
  string ret = 
    System.Convert.ToBase64String(enc.GetBytes(str));
  ret = string.Format("=?{0}?B?{1}?=", enc.BodyName, ret);
  return ret;
}

private void button2_Click(object sender, EventArgs e)
{
  System.Text.Encoding enc = 
    System.Text.Encoding.GetEncoding("utf-8");

  string mailAddress = this.userName + "@" + this.host;

  MailAddress to = 
    new MailAddress(mailAddress, EncodeHeader("日本花子", enc));
  MailAddress from = 
    new MailAddress(mailAddress, EncodeHeader("日本太郎", enc));

  MailMessage mailMessage = new MailMessage(from, to);

  mailMessage.Subject = textBox1.Text;
  mailMessage.Body = textBox2.Text;

  // 添付ファイル
  file = "テスト用の長めのファイル名です.pdf";
  FileStream fs = 
    new FileStream(file, FileMode.Open, FileAccess.Read);

  Attachment data = 
    new Attachment(fs, MediaTypeNames.Application.Octet);
  ContentDisposition disposition = data.ContentDisposition;
  disposition.FileName = 
    EncodeHeader(System.IO.Path.GetFileName(file), enc);

  mailMessage.Attachments.Add(data);

  SmtpClient smtpClient = new SmtpClient();
  smtpClient.Host = host;
  smtpClient.Port = portSmtp;

  try
  {
    smtpClient.Send(mailMessage);
    MessageBox.Show("Your mail was sent.");
  }
  catch (SmtpException ex)
  {
    SmtpStatusCode status = ex.StatusCode;
    MessageBox.Show("SmtpException: " + ex.Message + 
      "\nSmtpStatusCode: " + status.ToString());
  }
  catch (Exception ex)
  {
    MessageBox.Show("Exception: " + ex.Message);
  }
  finally
  {
    mailMessage.Dispose();
  }
}

こうすると、以下のように Content-Type の name は application/octet-stream となり、Content-Disposition の filename にファイル名が設定され、正しいファイル名を取得できます(メーラーによってはダメかもしれませんが)。

Content-Type: application/octet-stream;
 name="application/octet-stream"
Content-Transfer-Encoding: base64

Content-Disposition: attachment;
 filename="=?utf-8?B?44OG44...BkZg==?="

なお、一旦 Cotent-Type の name が二重エンコーディングされたものになってしまうと、Content-Disposition の filename にファイル名を設定しても、何故かそちらは無視されてしまいます。メーラーにもよると思いますが Windows メールはダメでした。

という訳で、上記のコード方法が次期バージョンがリリースされるまでの Workaround としては適当なようです(ただし、filename のところが、一行で 76 文字の制限を越えてしまいますので、メーラーによっては対応が必要かもしれません)。

Tags: ,

.NET Framework

SmtpClient でメール送信

by WebSurfer 2010年9月7日 14:25

ユーザー登録の際のパスワードの連絡、ブログにコメントが書き込まれた時の通知などのために、Web アプリケーションにメールの自動送信機能を実装するケースは多いと思います。

自分の HP にも、メールの自動送信の機能を実装しています。もっとも、ユーザー登録するような人はいないし、コメントを書き込む人もいないので、ほとんど役に立っていませんが。(苦笑)

メール送信に利用しているのは .NET Framework のライブラリ SmtpClient クラスです。これを利用すると簡単にメール送信ができます。

ただし、日本語対応に問題があり注意が必要です。以下に、文字コードとしてデフォルトの UTF-8 を使った場合と、日本で広く使われている iso-2022-jp (JIS) を使った場合の注意事項を書いておきます。

(1) UTF-8 を使う場合

UTF-8 の場合は、ほとんど文字化けに悩むことはありませんが、アドレスの表示名に日本語を使う場合のみ注意が必要です。

表示名は MailAddress コンストラクタの第 2 引数で設定しますが、日本語の文字列をそのまま代入すると Q エンコードされてしまいます。

Q エンコードで問題なければいいのですが、受信するメーラによっては B エンコードでないと文字化けするかもしれません。(Outlook Express, Windows メールは問題ありませんでしたが)

B エンコードにするには、以下のサンプルコードにある EncodeMailHeader ようなメソッドを作って日本語の表示名を変換し、それを第 2 引数に代入してください。

件名、本文、添付ファイル名は、以下のサンプルのように日本語をそのまま代入して問題ありません。件名、添付ファイル名は、自動的に UTF-8 の B エンコードに変換されて、ヘッダーに設定されます。本文は、UTF-8 の base64 エンコードとなります。

private string EncodeMailHeader(string str, Encoding enc)
{
  string ret = 
    System.Convert.ToBase64String(enc.GetBytes(str));
  ret = string.Format("=?{0}?B?{1}?=", enc.BodyName, ret);
  return ret;
} 

Encoding enc = Encoding.GetEncoding("utf-8");

MailAddress to = 
  new MailAddress("surfer@mail.abc.co.jp", 
    EncodeMailHeader("日本語の名前", enc));
MailAddress From = 
  new MailAddress("surfer@mail.abc.co.jp", 
    EncodeMailHeader("日本語の名前", enc));

MailMessage mailMessage = new MailMessage(from, to);

mailMessage.Subject = "日本語の件名";
mailMessage.Body = "日本語の本文";

Attachment data = 
  new Attachment("添付ファイル名", 
    System.Net.Mime.MediaTypeNames.Application.Octet);
ContentDisposition disposition = data.ContentDisposition;
disposition.FileName = 
  EncodeMailHeader(System.IO.Path.GetFileName(file), enc);
disposition.CreationDate = 
  System.IO.File.GetCreationTime(file);
disposition.ModificationDate = 
  System.IO.File.GetLastWriteTime(file);
disposition.ReadDate = 
  System.IO.File.GetLastAccessTime(file);

mailMessage.Attachments.Add(data);

SmtpClient smtpClient = new SmtpClient();
smtpClient.Host = "mail.abc.co.jp";
smtpClient.Port = 25;
smtpClient.Send(mailMessage);

(2) iso-2022-jp を使う場合

Encoding を iso-2022-jp にする以外は、メールアドレスを設定するところまでは上記の UTF-8 の場合と同じです。

件名は、直接 Subject プロパティに日本語を代入すると Q エンコードになってしまいます。以下のサンプルのように、EncodeMailHeader メソッドを使って変換してから Subject プロパティに代入しなければなりません。(注:.NET Framework 4.5 で件名のエンコードが変わったそうです。詳しくは下の 2013/5/10 追記を見てください)

一番の問題は本文で、Body を使用すると Content-Transfer-Encoding は 8bit エンコードになってしまいます。これを 7bit にする方法はないそうです。代わりに、AlternateView を使って 7bit エンコードにする方法があります。以下にそのサンプルを書いておきます。

添付ファイル名は、Content-Type の name は、どのよう設定にしても UTF-8 の B エンコードになってしまいます。一方、Content-Disposition の filename は、以下のように設定すれば、iso-2022-jp の B エンコードになります。

以上のように、iso-2022-jp を使うのは、特に本文を AlternateView にするところが、かなり無理やりっぽい感じです。自分的には決してお勧めではないです。

Encoding enc = Encoding.GetEncoding("iso-2022-jp");

private string EncodeMailHeader(string str, Encoding enc)
{
  string ret = 
    System.Convert.ToBase64String(enc.GetBytes(str));
  ret = string.Format("=?{0}?B?{1}?=", enc.BodyName, ret);
  return ret;
}

MailAddress to = 
  new MailAddress("surfer@mail.abc.co.jp", 
    EncodeMailHeader("日本語の名前", enc));
MailAddress From = 
  new MailAddress("surfer@mail.abc.co.jp", 
    EncodeMailHeader("日本語の名前", enc));

MailMessage mailMessage = new MailMessage(from, to);

mailMessage.Subject = EncodeMailHeader("日本語の件名", enc);

// プレーンテキストのAlternateViewを作成
AlternateView htmlView = 
  AlternateView.CreateAlternateViewFromString(
    "日本語の本文",
    enc,
    System.Net.Mime.MediaTypeNames.Text.Plain);

// TransferEncoding.SevenBitを指定
htmlView.TransferEncoding = 
  System.Net.Mime.TransferEncoding.SevenBit;

mailMessage.AlternateViews.Add(htmlView);

Attachment data = 
  new Attachment("添付ファイル名", 
    System.Net.Mime.MediaTypeNames.Application.Octet);
ContentDisposition disposition = data.ContentDisposition;
disposition.FileName = 
  EncodeMailHeader(System.IO.Path.GetFileName(file), enc);
disposition.CreationDate = 
  System.IO.File.GetCreationTime(file);
disposition.ModificationDate = 
  System.IO.File.GetLastWriteTime(file);
disposition.ReadDate = 
  System.IO.File.GetLastAccessTime(file);

mailMessage.Attachments.Add(data);

SmtpClient smtpClient = new SmtpClient();
smtpClient.Host = "mail.abc.co.jp";
smtpClient.Port = 25;
smtpClient.Send(mailMessage);

ということで、結局自分はどうしているかというと、デフォルトの UTF-8 を使っています。UTF-8 が使えないメーラーなんか相手にしない・・・と言いたいところですが、自分宛のメール送信ぐらいにしか使ってないから、何だっていいのです。(笑)

----- 2013/5/10 追記 -----

.NET Framework 4.5 では件名 (Subject) の実装が変わって、上記の「(2) iso-2022-jp を使う場合」に書いたサンプルコードでは Q エンコードになってしまうそうです。その理由など、詳しくは以下のページを参照してください。

.NET Framework 4.5 の System.Net.Mail で日本語の件名を ISO-2022-JP の Base64 でエンコードして送信する方法

二重にエンコードすることにより B エンコードになるそうです。検証していませんが、上記のサンプルコードでは以下のように変更すればよいはずです。

.NET Framework 4 まで(上記サンプルコードの通り)

mailMessage.Subject = EncodeMailHeader("日本語の件名", enc);

.NET Framework 4.5 では二重エンコード

mailMessage.Subject = 
  EncodeMailHeader(EncodeMailHeader("日本語の件名", enc), enc);

Tags: , ,

.NET Framework

メール送信

by WebSurfer 2010年5月4日 14:51

「コンタクト」メニューからのメール送信ができるように設定しました。

メール送信は contact.aspx.cs で SmtpClient クラスを利用して行うようになっています。 ところが SmtpClient と自分の使っている ISP の SMTP サーバーとの相性が悪く、以前、趣味のページの Web アプリからメール送信 で書いた SMTP AUTH の問題のためダメでした。

同じようにコードを書き換えれば問題は解決しますが、古い形式で使用は推奨されてない SmtpMail を使うのも何なので、SMTP サーバの方を、このホームページのホスティングサービスでおまけ(?)についてくる SMTP サーバに変えて解決しました。

SMTP AUTH の相性の問題で SmtpClient が使えないという話は MSDN フォーラムでも聞いたことがないのですが、自分はよほど運が悪いんでしょうか。(汗)

Tags: , ,

BlogEngine.NET

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年3月  >>
252627282912
3456789
10111213141516
17181920212223
24252627282930
31123456

View posts in large calendar