はじめに
先日メールファイルの読み込みと処理を行うプログラムのソースコードを読む機会がありました。
その際、メールのエンコード方式について学ぶ必要があったので、今回まとめてみました。
8bitから7bitへ
人間にとって読み取り可能なテキストを通信機器でやりとりできるようにするためには、データ形式の変換を行う必要があります。
エンコードといえば、文字化けの対処法としてエンコード方式や文字コードを変更する、というのは一般的に馴染みがあると思います。
代表的な文字コードであるutf-8やISO-2022-JPは、それぞれ膨大な文字集合に含まれるコードポイントという番号を使って特定の文字を暗号化し、コンピューターでの処理を可能にするための変換方式です。
それとは別に、メールにはメール特有のエンコードが行われています。
メールはSMTPという専用のプロトコルでやりとりされていますが、
デフォルトでサポートされているのは7bitで表現できるASCII文字(半角ローマ字や数字等)のみとなっています。
電子メールは当初、主に英語で書かれたテキストのやりとりを想定していたからです。
日本語や中国語のようにローマ字と比較にならないほど情報量が多く、7bitでは表現できないような文字集合を使って書かれたテキストデータを損なうことなく着実に通信を行うためには、7bitへ変換する必要があります。
こうしたエンコードの情報は、メールヘッダー内で以下のように表示されます。
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable
それぞれ順に見ていきます。
Content-Type
テキストメール、HTMLメール、添付ファイルのあるメール等の種類を指定します。
今回の例のようにtext/plainとなっている場合は、文字化けが起きないように、テキストがどのような文字集合で記述されているのか明示する必要があります。
上の例では、文字セットにutf-8が指定されています。
Content-Transfer-Encoding
メールのデータをSMTPプロトコルで送信できるような形に変換する際のエンコード方式を指定します。
よく使われるものには、quoted-printableや、画像や音声データをSMTP用に変換するbase64等があります。
Pythonライブラリを使ったencode/decode
quoted-printableは非ASCII文字で表現されたテキストデータを7bitすなわちASCII文字で表現するためのエンコード方式です。
テキストに含まれる文字の、指定されたエンコード方式におけるコードポイントの16進数表記の前に半角イコール「=」をつけます。
例えば、
=E3=81=8F=E3=82=8D=E3=82=8A=E3=81=AF=E5=A4=A7=E3=81=8D=E3=81=AA=E7=8C=AB=
=E3=81=A7=E3=81=99=E3=80=82
こんなふうになります。
何と書いてあるでしょうか?Pythonのquopriモジュールを使ってutf-8に変換してみましょう。
import quopri
# quoted-printableでエンコードされたASCII文字列
encoded_bytes = "=E3=81=8F=E3=82=8D=E3=82=8A=E3=81=AF=E5=A4=A7=E3=81=8D=E3=81=AA=E7=8C=AB=\
=E3=81=A7=E3=81=99=E3=80=82"
# quoted-printableでデコードし、Bytes型オブジェクトを出力
decoded_bytes = quopri.decodestring(encoded_bytes)
print(decoded_bytes)
# '\x'は16進数のエスケープ表現
# b'\xe3\x81\x8f\xe3\x82\x8d\xe3\x82\x8a\xe3\x81\xaf\xe5\xa4\xa7\xe3\x81\x8d\xe3\x81\xaa\xe7\x8c\xab\xe3\x81\xa7\xe3\x81\x99\xe3\x80\x82'
# 出力したBytes型オブジェクトをutf-8でデコードし、文字列を出力
decoded_str = decoded_bytes.decode('utf-8')
print(decoded_str)
# くろりは大きな猫です。
ちなみにASCII文字は元々7bitで表現されるため、変換されません。
ローマ字で構成される言語の場合、アクセントがついた母音等の特殊文字のみ変換が行われます。
import quopri
# フランス語の文字列
str = "Les chaussettes de l’archiduchesse, sont-elles sèches? Archi-sèches."
# utf-8でエンコードし、Bytes型オブジェクトを出力
encoded_str = str.encode('utf-8')
print(encoded_str)
# b'Les chaussettes de l\xe2\x80\x99archiduchesse, sont-elles s\xc3\xa8ches? Archi-s\xc3\xa8ches.'
# quoted-printableでエンコードし、Bytes型オブジェクトを出力
encoded_bytes = quopri.encodestring(encoded_str)
print(encoded_bytes)
# b'Les chaussettes de l=E2=80=99archiduchesse, sont-elles s=C3=A8ches? Archi-s=\n=C3=A8ches.'
16進数の「e3」や「80」を、「=E3」や「=80」のような形に変換しているのがわかると思います。
以下、参考になりました。