VB2010 メール送信 添付ファイル名文字化け

解決


まさお  2012-08-01 17:07:19  No: 147707  IP: [192.*.*.*]

まさお と申します。  宜しくお願い致します。

  WinXP Pro SP3    VB2010 Express SP1 です。

  http://support.microsoft.com/kb/933866/ja  
「MailMessage を使ってメッセージを送信すると送受信者名、件名が文字化けする」
「エンコードに "iso-2022-jp" を使用すると…添付ファイル名…が文字化けする場合があります」
という記事があります。

  件名等の対処方法はここに記されているので判ります。
  しかし、添付ファイル名の対処方法が判りません。
  対処方法がお判りになる方、ご教授をお願い致します。


以上、宜しくお願い致します。

編集 削除
みけ  2012-08-04 18:33:27  No: 147708  IP: [192.*.*.*]

添付ファイル名も同じくBエンコーディングで
エンコードして送信すれば文字化けは起こらないです。

.NETのメール送信クラスにこだわらないのであれば
nMail.dll (フリー/商用の場合シェア)などの.NET用
メール送信クラスを利用すれば添付ファイル名の
文字化けはせずに送れると思います。

編集 削除
まさお  2012-08-06 09:41:17  No: 147709  IP: [192.*.*.*]

みけ さん、ありがとうございます。

> 添付ファイル名も同じくBエンコーディングで
> エンコードして送信すれば文字化けは起こらないです。
>
  添付ファイル名をエンコードすると「ファイルが見つからない」エラーになってしまい、どうすれば良いのかが私には判らないのです。
  未だ試行錯誤しています。

> nMail.dll (フリー/商用の場合シェア)などの…
>
  アドバイスありがとうございます。
  System.Net.Mail.MailMessage でどうしても解決しなかったら外部dllの使用を検討してみますが今は未だチャレンジしたいと考えています。
      #既に意地になっている状況です。


引き続きアドバイス戴ければ幸いです。

編集 削除
まさお  2012-08-06 10:44:29  No: 147710  IP: [192.*.*.*]

自己レスです。

> 「ファイルが見つからない」エラー
>
  間違えました。
  「パスに無効な文字が含まれています」でした。

  以下のようにしているのですが何処を直したら良いのかアドバイス戴きたくお願い致します。
  ※部でエラーになります。

Public Sub MailTest()
    Dim Ans As String
    Dim MyAttach As System.Net.Mail.Attachment
    Dim AttachFullPath As String = "c:\test.txt"
    Try
        AttachFullPath = EncordB(AttachFullPath)
        MyAttach = New Attachment(AttachFullPath) '※
        Ans = "OK"
    Catch ex As Exception
        Ans = ex.Message
    End Try
    MsgBox(Ans)
End Sub
Private Function EncordB(ByVal strTarget As String) As String
    Dim arrbJisSubject() As Byte    ' iso-2022-jp に変換した件名
    Dim strEncSubject As String     ' B エンコードした件名
    arrbJisSubject = System.Text.ASCIIEncoding.GetEncoding("iso-2022-jp").GetBytes(strTarget)
    strEncSubject = "=?ISO-2022-JP?B?" & _
                    Convert.ToBase64String(arrbJisSubject) & _
                    "?="
    Return strEncSubject
End Function

編集 削除
瀬戸っぷ  2012-08-07 13:15:19  No: 147711  IP: [192.*.*.*]

>「パスに無効な文字が含まれています」でした。

添付ファイル名にドライブ名とか、パス名とかは意味がありませんが……

http://msdn.microsoft.com/ja-jp/library/system.net.mail.attachment%28v=vs.80%29.aspx

Attachment()のコンストラクタでは、パス名なしのファイル名(をエンコードしたもの)でインスタンスを生成し、
データ自体はContentDispositionクラスで(フルパスでファイル名を指定して)取り込む…のではないかと。

編集 削除
まさお  2012-08-09 10:04:30  No: 147712  IP: [192.*.*.*]

瀬戸っぷ さん、ありがとうございます。

> Attachment()のコンストラクタでは、パス名なしのファイル名(をエンコードしたもの)でインスタンスを生成し、
>
  私が 2012/08/06(月) 10:44:29 に投稿したコードの4行目を
  Dim AttachFullPath As String = "test.txt"
とファイル名だけにしても※で「パスに無効な文字が含まれています」が発生します。


> データ自体はContentDispositionクラスで(フルパスでファイル名を指定して)取り込む

http://msdn.microsoft.com/ja-jp/library/system.net.mail.attachment%28v=vs.80%29.aspx
>
  リンク先紹介ありがとうございました。
  試行してみます。

  現在[ client.Credentials = CredentialCache.DefaultNetworkCredentials; ]で引っかかっています。
  vb.net にすれば
  client.Credentials = New CredentialCache.DefaultNetworkCredentials
となると思うのですが、「System.Net.ICredentialsByHostにキャストできない」エラーが発生しています。
  .Credentials が何なのか全く判らないのでこれから調べます。


  その他、追加アドバイスがあれば宜しくお願い致します。

編集 削除
YuO  2012-08-09 10:30:12  No: 147713  IP: [192.*.*.*]

元々ファイル名をBエンコード「してはいけない」ので,
Attachmentクラスのコンストラクタには,
エンコードしないファイル名を渡してください。

編集 削除
まさお  2012-08-09 16:34:47  No: 147714  IP: [192.*.*.*]

YuOさん、ありがとうございます。

> エンコードしないファイル名を渡してください。

  ハイ。  それは ex.Message を見れば判るのですが、ではエンコードしたファイル名をどう処理したら良いのか、が判らなくて困っています。

  瀬戸っぷ さんアドバイスにより、disposition.FileName に Let するのか?  と今は思っていますが、「System.Net.ICredentialsByHostにキャストできない」エラーが解決できていないので試行完了していません。

編集 削除
瀬戸っぷ  2012-08-09 17:05:34  No: 147715  IP: [192.*.*.*]

.NET Framework 4なら
http://msdn.microsoft.com/ja-jp/library/system.net.mail.attachment.aspx
こっちでしたかねぇ。

>  .Credentials が何なのか全く判らないのでこれから調べます。

SMTP認証する場合の、認証情報…っぽいです。

http://msdn.microsoft.com/ja-jp/library/system.net.credentialcache.aspx

デフォルトを使わないのであれば、CredentialCacheのインスタンスを生成してAddメソッドで追加…でしょうかね。
http://msdn.microsoft.com/ja-jp/library/59x2s2s6.aspx

いろいろ準備が必要そうですね……。

編集 削除
まさお  2012-08-09 17:05:43  No: 147716  IP: [192.*.*.*]

自己レスです。
http://msdn.microsoft.com/ja-jp/library/system.net.mail.attachment%28v=vs.80%29.aspx
を参考にエラー発生しないコードにしてメール送信だけはできるようにしました。
  下記にすると添付ファイル名が化けます。
  エンコードしたファイル名はどう使えば良いのかアドバイス戴ければ幸いです。


Public Sub CreateMessageWithAttachment(ByVal server As String)
    Dim file As String = "ほげほげ.xls"

    'Create a message and set up the recipients.
    Dim message As MailMessage = _
        New MailMessage( _
            "jane@contoso.com", _
            "ben@contoso.com", _
            "Quarterly data report.", _
            "See the attached spreadsheet.")

    'Create  the file attachment for this e-mail message.
    Dim data As Attachment = New Attachment(file, MediaTypeNames.Application.Octet)

    Dim disposition As ContentDisposition = data.ContentDisposition
    disposition.FileName = EncordB(file)

    'Add the file attachment to this e-mail message.
    message.Attachments.Add(data)
    'Send the message.
    Dim client As SmtpClient = New SmtpClient(server)
    'Add credentials if the SMTP server requires them.
    client.Send(message)
    data.Dispose()
End Sub
Private Function EncordB(ByVal strTarget As String) As String
    Dim arrbJisSubject() As Byte    ' iso-2022-jp に変換した件名
    Dim strEncSubject As String     ' B エンコードした件名
    ' 件名を iso-2022-jp に変換します。
    arrbJisSubject = System.Text.ASCIIEncoding.GetEncoding("iso-2022-jp").GetBytes(strTarget)
    ' iso-2022-jp に変換した文字列を Base64 エンコードし、エンコードを表す文字列を追加します。
    strEncSubject = "=?ISO-2022-JP?B?" & Convert.ToBase64String(arrbJisSubject) & "?="
    Return strEncSubject
End Function

編集 削除
YuO  2012-08-09 17:16:15  No: 147717  IP: [192.*.*.*]

なんでContent-DispositionのFileNameにBエンコードしているのかがわかりません。
RFC 2231はちゃんと読んだのでしょうか……。
http://www.ietf.org/rfc/rfc2231.txt
http://www.emaillab.org/essay/japanese-filename.html

編集 削除
まさお  2012-08-09 17:21:38  No: 147718  IP: [192.*.*.*]

追加自己レスです。

2012/08/09(木) 17:05:43に投稿したコードでメール送信すると、
  GoogleMailで受信:添付ファイル名が化ける。
  MS-Outlookで受信:添付ファイル名正常
となりました。

  何が原因でこのようになるのか判りませんが、さらに試行を続けます。

    ※MSが公開しているエンコード方法がMSアプリにのみ有効、という訳ではないと思いたいです。

編集 削除
まさお  2012-08-09 17:25:47  No: 147719  IP: [192.*.*.*]

瀬戸っぷ さん、ありがとうございます。
戴いたアドバイスで試行してみます。

YuO さん、ありがとうございます。
> RFC 2231はちゃんと読んだのでしょうか……。
イエ、読んでいません。(TT)
これから読みます。

編集 削除
まさお  2012-08-10 15:52:25  No: 147720  IP: [192.*.*.*]

瀬戸っぷ さん、ありがとうございます。
> .NET Framework 4なら
>
  ハイ。  最初に私が作ったコードは概略以下です。
    Public Sub SendMail(ByVal MailSmtp As String, _
                        ByVal MailFrom As String, _
                        ByVal MailTo As String, _
                        ByVal AddFile As String)
        Dim message As New System.Net.Mail.MailMessage
        Dim attachment As System.Net.Mail.Attachment = New System.Net.Mail.Attachment(AddFile)
        attachment.NameEncoding = System.Text.Encoding.GetEncoding("iso-2022-jp")
        With message
            .From = New System.Net.Mail.MailAddress(MailFrom)
            .To.Add(New System.Net.Mail.MailAddress(MailTo))
            .Subject = "test"
            .Body = "this is test"
            .Attachments.Add(attachment)
        End With
        Dim client As System.Net.Mail.SmtpClient = New System.Net.Mail.SmtpClient(MailSmtp)
        client.Send(message)
    End Sub

  このコードで和文名ファイルを添付するとファイル名化けとなり、ネットサーフィンして最初の投稿に記した、
  http://support.microsoft.com/kb/933866/ja  に行き着いた、という次第です。


更に追加アドバイスがあれば宜しくお願い致します。

編集 削除
まさお  2012-08-10 16:37:27  No: 147721  IP: [192.*.*.*]

YuO さん、ありがとうございます。

> 元々ファイル名をBエンコード「してはいけない」ので,
>
  このアドバイスの意味が判りました。
  しかも「」付きであることに今気が付きました。
  と、すると、何の為に System.Net.Mail.Attachment に NameEncodingプロパティ があるのか…?

  理解は致しましたが和文名ファイルをなんとか添付したいと考えています。
  現実的に外部DLL(現在TKMP.dll使用中)を使えばファイル名が化けないで和文名ファイルを添付できるので、
      ※ファイル名が化けないことを保証されている訳ではない、ということも理解しましたが。
なんとかチャレンジしたいと考えています。
      ただ、さすがに挫折しかけていますが。


更に追加アドバイスがあれば宜しくお願い致します。

編集 削除
魔界の仮面弁士  2012-08-10 18:49:17  No: 147722  IP: [192.*.*.*]

お気づきとは思いますが、添付ファイル名の扱いはいろいろと難しい所で、

・本来は RFC2231 で書かれるべき。(ただし、非対応のメーラーも未だ存在する)
・MIME(Bエンコード)なら読めるメーラーもある。(ただしそれは RFC 違反な実装である)
・稀に、UUENCODE や BinHex が採用されているメーラーさえ存在する。

という状況なんですよね。ゆえに PC だと受信できて、携帯だと化けるなんてことも。



> Private Function EncordB(ByVal strTarget As String) As String

EncordB ではなく、EncodeB あるいは BEncode では無いかな…?



> strEncSubject = "=?ISO-2022-JP?B?" & Convert.ToBase64String(arrbJisSubject) & "?="

YuO さんが紹介された URL  http://www.emaillab.org/essay/japanese-filename.html  を見ると、
RFC 2231 で "ほごほげ.jpeg" を送出するために、以下の例示がありますね。

Content-Disposition: attachment;
  filename*=iso-2022-jp'ja'%1B%24B%24%5B%244%24%5B%242%1B%28B.jpeg


一方、まさおさんが書かれた
>  strEncSubject = "=?ISO-2022-JP?B?" & Convert.ToBase64String(arrbJisSubject) & "?="
の結果は『strEncSubject = "ほごほげ.jpeg"』となります。


試しに、HttpUtility.UrlEncode メソッドで変換してみました。
これだと「(」と「)」は置き換わらないので、追加変換が必要にはなりますが、
『s = System.Web.HttpUtility.UrlEncode("ほごほげ.jpeg", System.Text.Encoding.GetEncoding(50220)).Replace("(", "%28").Replace(")", "%29")』
を実行することで、『s = "%1b%24B%24%5b%244%24%5b%242%1b%28B.jpeg"』を得ることができます。


…ただし残念ながら、RFC2231 にて定義されているパーセントエンコーディング部分は
  ext-octet := "%" 2(DIGIT / "A" / "B" / "C" / "D" / "E" / "F")
であって、
  ext-octet := "%" 2(DIGIT / "A" / "B" / "C" / "D" / "E" / "F" / "a" / "b" / "c" / "d" / "e" / "f" )
ではないので、このまま使う事はできません。

かといって単純に .ToUpperInvariant() してしまうと、
"%1B%24B%24%5B%244%24%5B%242%1B%28B.jpeg" ではなく
"%1B%24B%24%5B%244%24%5B%242%1B%28B.JPEG" になってしまうので、
自前で変換するか、"%??" 単位で大文字化する処理のいずれかが必要そうです。


なお、この変換作業によって送信できるようになるかどうかは分かりません。
(手元にメール送信可能な環境が無いため、検証できません…)



> 理解は致しましたが和文名ファイルをなんとか添付したいと考えています。

相手のメーラーで RFC2231 が使えるかどうかが事前に分からないような状況では、
残念ながら『日本語を使わない』という後ろ向きな選択をした方が無難なのですが、
どうしても日本語のファイル名を送付する必要がある場合には、それらを
英数字名の LZH / ZIP / CAB ファイル等に固めて添付するという手もあります。


とはいえ、実は ZIP にも文字コード問題があるのですけれどね…。
https://connect.microsoft.com/VisualStudio/feedback/details/711235/system-io-ziparchive-zipped-only-utf-8-encoding

# 本来は、ZIP内部のファイル名には CP437 しか使えない仕様のはずだが、実際には各国で
# まちまちな文字コードにて利用されている(国内では Shift_JIS が使われる事が多い)ため、
# いざ解凍しようとすると、文字コード判定に失敗して文字化けを起こしてしまうという罠。
# (PKWARE 6.3.0 以降では UTF-8 も使えるが、WinXP の ZIP フォルダ等は UTF-8 未対応)

編集 削除
魔界の仮面弁士  2012-08-10 19:54:12  No: 147723  IP: [192.*.*.*]

System.Net.Mail 経由で送信できるかどうかは未検証ですが、
「ほごほげ.jpeg」→「%1B%24B%24%5B%244%24%5B%242%1B%28B.jpeg」
への変換関数を作ってみました。参考までに。


'Imports System.IO
'Imports System.Text


Private Function PercentEncode(text As String) As String
    Return UrlEncode(text, Encoding.GetEncoding(50220))
End Function

Private Function PercentEncode(text As String, encode As Encoding) As String
    Dim ms As New MemoryStream()
    Dim sw As New StreamWriter(ms, Encoding.ASCII)
    sw.AutoFlush = True
    For Each b In encode.GetBytes(text)
        Select Case b
            '     "0" To  "9",  "A" To  "Z",  "a" To  "z",  "-",  ".",  "_",  "~"
            Case &H30 To &H39, &H41 To &H5A, &H61 To &H7A, &H2D, &H2E, &H5F, &H7E
                ms.WriteByte(b)
            Case Else
                sw.Write("%" & b.ToString("X2"))
        End Select
    Next
    ms.Position = 0
    Return New StreamReader(ms, Encoding.ASCII).ReadToEnd()
End Function

編集 削除
まさお  2012-08-20 14:38:56  No: 147724  IP: [192.*.*.*]

魔界の仮面弁士 さん、ありがとうございます。

  レスが遅くて誠に申し訳ありません。


> EncordB ではなく、EncodeB あるいは BEncode では無いかな…?
>
  ご指摘ありがとうございます。  お恥ずかしい限りです。


> への変換関数を作ってみました。参考までに。
>
  コードのご提示ありがとうございました。
  おんぶに抱っこで申し訳ないのですが、この関数をどう使ったら良いかをご教授戴けないでしょうか?
  私が根本を理解していない、というのは判っているのですが何をどうすれば良いのか判らないのです。  

  下記コードでは【Base-64ストリームに無効な文字がある】エラーになります。
  ※部αをFalseにすると(※β部を実行)受信メールの添付ファイル名が「%1B%24B%24%5B%244%24%5B%242%1B%28B.jpeg」になってしまいます。
  さらにβをFalseにすると(※γ部を実行)【パスに無効な文字が含まれている】エラーになります。
  さらにγをFalseにすると(※δ部を実行)受信メールの添付ファイル名が化けます。
      実際には"ほごほげ.jpeg"程度の短いファイル名では化けないのですが、もっと長いファイル名
            例:ほごほげほごほげほごほげほごほげ.jpeg
      だと化けてしまいます。

Private Sub SendMail1(ByVal MailSmtp As String, _
                      ByVal MailFrom As String, _
                      ByVal MailTo As String)

    Dim MailFile As String = "ほごほげ.jpeg"
    Dim EnCodeFile As String
    Dim message As New System.Net.Mail.MailMessage

#If True Then '※α
    EnCodeFile = "=?ISO-2022-JP?B?" & Me.PercentEncode(MailFile) & "?="
    Dim attachment As System.Net.Mail.Attachment = New System.Net.Mail.Attachment(MailFile)
    attachment.Name = EnCodeFile

#ElseIf True Then '※β
    EnCodeFile = Me.PercentEncode(MailFile)
    Dim attachment As System.Net.Mail.Attachment = New System.Net.Mail.Attachment(MailFile)
    attachment.Name = EnCodeFile

#ElseIf True Then '※γ
    EnCodeFile = "=?ISO-2022-JP?B?" & Me.PercentEncode(MailFile) & "?="
    Dim attachment As System.Net.Mail.Attachment = New System.Net.Mail.Attachment(EnCodeFile)

#Else '※δ
    Dim attachment As System.Net.Mail.Attachment = New System.Net.Mail.Attachment(MailFile)
#End If

    REM attachment.NameEncoding = System.Text.Encoding.GetEncoding("iso-2022-jp")
    With message
        .From = New System.Net.Mail.MailAddress(MailFrom)
        .To.Add(New System.Net.Mail.MailAddress(MailTo))
        .Subject = "test"
        .Body = "this is test"
        .Attachments.Add(attachment)
    End With
    Dim client As System.Net.Mail.SmtpClient = New System.Net.Mail.SmtpClient(MailSmtp)
    client.Send(message)
End Sub 'SendMail1

Private Function PercentEncode(text As String) As String
    Return PercentEncode(text, System.Text.Encoding.GetEncoding(50220))
    '貴提示コードでは UrlEncode ですが、Overloads で下のコードをCallしていると判断しました。
End Function

Private Function PercentEncode(text As String, encode As System.Text.Encoding) As String
    Dim ms As New System.IO.MemoryStream()
    Dim sw As New System.IO.StreamWriter(ms, System.Text.Encoding.ASCII)
    sw.AutoFlush = True
    For Each b In encode.GetBytes(text)
        Select Case b
            '     "0" To  "9",  "A" To  "Z",  "a" To  "z",  "-",  ".",  "_",  "~"
            Case &H30 To &H39, &H41 To &H5A, &H61 To &H7A, &H2D, &H2E, &H5F, &H7E
                ms.WriteByte(b)
            Case Else
                sw.Write("%" & b.ToString("X2"))
        End Select
    Next
    ms.Position = 0
    Return New System.IO.StreamReader(ms, System.Text.Encoding.ASCII).ReadToEnd()
End Function

編集 削除
まさお  2012-08-31 10:53:29  No: 147725  IP: [192.*.*.*]

解決はしておりませんが追加のアドバイスを戴けないので閉じます。

アドバイスして戴いた先輩諸兄、どうもありがとうございました。

編集 削除
魔界の仮面弁士  2012-08-31 14:47:28  No: 147726  IP: [192.*.*.*]

> どうしても解決しなかったら外部dllの使用を検討してみますが
外部 DLL の利用に踏み切ったということですね。


> もっと長いファイル名だと化けてしまいます。
原因は、先の
http://www.emaillab.org/essay/japanese-filename.html
にある、長い文字列を複数行表記した場合の

Content-Disposition: attachment;
  filename*0*=iso-2022-jp'ja'%1B%24B%24%5B%244%24%5B%242%1B%28B;
  filename*1=.jpeg



Content-Type: application/x-stuff;
 title*0*=us-ascii'en'This%20is%20even%20more%20;
 title*1*=%2A%2A%2Afun%2A%2A%2A%20;
 title*2="isn't it!"

といった分割処理が、内部的にうまく働かないためでしょう。
プログラマ側がエンコードした文字列を渡しているので、
どこを区切れば良いのか判断できないのかもしれません。


ただ、それを System.Net.Mail.MailMessage で回避できるかどうかについては、
私自身は一切情報を持っていませんし、検証もできる状態にもありません。
そのため、この件に関しての具体的な回答を控えていました。m(_ _)m

編集 削除
まさお  2012-09-03 10:08:54  No: 147727  IP: [192.*.*.*]

魔界の仮面弁士 さん、ありがとうございます。

> 外部 DLL の利用に踏み切ったということですね。

  ハイ。  TKMP.dll を使用しています。
  このDLLを使用すれば長い和文名ファイルを添付してもファイル名が化けることはないので何らかの方法論はあるはずですが、現在の私の理解力では実現することができません。
  もっと勉強して出直します。

> そのため、この件に関しての具体的な回答を控えていました。m(_ _)m

  わざわざご連絡くださりありがとうございました。
  vb.net は始めたばかりで判らない事だらけです。
  これからもここに質問させて戴く事は多々あると思います。
  その際にまたアドバイス戴ければ幸いです。

編集 削除