doc drawn up: 2003-03-07 .. 2005-12-12

Mail 規格に関する覚え書き

依然、1 バイト= 7 ビットを原則とする RFC 2822

Email に関する規格の歴史は古い。アメリカで成立した物なので、US-ASCII 文字コードを基本としている 7 ビットを 1 バイトとして扱う()規格であるという事実は、更新された RFC 2822 についても変わっていない。。この 7 ビットの文字コード体系である US-ASCII 文字に対して、英語以外の欧語圏のような 8 ビットの文字コード体系は、US-ASCII コード番号と互換性を保っており、最上位ビット(8 ビット目)に 0 が加わっているだけで、下位 7 ビットは US-ASCII とビットの並びが同じになるように設計されている。すなわち、最上位ビットが 0 で始まるコード番号の領域(0..127)には、US-ASCII と同じ通常の英数字記号と制御コントロール文字()が格納されている。そして、最上位ビットが 1 で始まるコード番号の領域(128..255)に、英語では元々必要とされなかった各国語の例えばフランス語の発音記号などを割り当てているのである。

ところが、先述したように、Email 技術はアメリカで主に発祥・普及したものであるため、7 ビットの US-ASCII 文字コード体系を暗黙の前提としている。8 ビットを前提とするシステムから 7 ビットを前提とするシステムを経て、再び 8 ビットを前提とするシステムに文字データを送るような場合を考えてみる()。この場合、途中の 7 ビットのシステムで 8 ビットのシステムから文字データを受け取る際に、最上位ビットが 0 であれ 1 であれ、下位の 7 ビットだけを 1 バイト分の文字データとして受け取るという変換作業が行われる。次に、7 ビットのシステムから 8 ビットのシステムが文字データを受け取る際には、8 ビットのシステムは、7 ビットのデータに最上位ビットに 0 を追加して 8 ビット分の文字データに変換するするという作業を行って受け取るのである。この結果、最上位ビットが 1 で始まるコード番号の領域(128..255)の文字はすべて文字化けする結果になる。

このような 7 ビットを大前提とする RFC 2822 に従いつつも、8 ビットのデータを扱おうとするためには、拡張規定である MIME(後述)を用いることになる。

2 バイト文字

例えば、日本や中国のような漢字文化圏の国の文字コードの場合、8 ビットの文字コード体系でも足りない。そこで、16 ビットの文字コード体系を採用したりしている。言ってみれば、16 ビットを 1 バイトとして扱うと言ってもいいのだが()、8 ビット = 1 バイトが世界的に常識になってきていることから、8 ビットの倍数として 16 ビットを選択したという感じではあるので、2ダブル バイト文字と呼ばれるのが普通である()。現在世界的に普及しつつある多言語文字コード Unicode の一種である UTF-8、UNIX 系のシステムで従来から使われている EUC-JP や、Windows/Macintosh で従来から使われている Shift_JIS などがこれらに相当する。

つまり、8 ビットの倍数を 1 バイト単位として用いていることになるので、前述の 8 ビットの文字コード体系が有するメール送受信における文字化けの危険性を、同様に有していることになる。

では、8 ビットの倍数の 16 ビットという数字を選ぶのではなくて、7 ビットの倍数の例えば 14 ビットを 1 バイトとして扱うような文字コード体系にしてしまえばいいのじゃないか──それが、ISO-2022-JP という文字コード体系の発想だと思えばいい。実際には 7 ビットの文字 2 文字に分解されて扱われるので、7 ビットのシステムを経由したとしても、文字化けすることはない。8 ビットのシステムにおいては最上位ビットに 0 が補われて扱われるだけであり、8 ビットのシステムにおいて最上位ビットが 1 となるコード番号は使用しないのである。

MIME (Multipurpose Internet Mail Extensions)

ISO-2022-JP のアプローチは、Email に関する規格が、US-ASCII が 7 ビットを 1 バイトとして扱うコード体系であるという事実に全く逆らわずに、バイトを倍数化して利用することでコード番号を拡張する方法である。一方、非英語の欧語圏では、いわば 1 バイトに固執し、1 バイトの定義を 7 ビットとするか 8 ビットとするかという主義上の隔たりをいかにして埋めるかというアプローチを採った。それが MIME(多目的インターネットメール拡張)である。

これはすなわち、US-ASCII コード体系を前提とする Email の 7 ビットの(128 通りの文字を格納する)小さな容器に、2 倍の 8 ビット(256 通り)の文字を詰め込もうとするための拡張方法である。7 ビットという容器の小ささに着目しその容器の大きさを拡張するという ISO-2022-JP のような発想ではなしに、8 ビットという文字集合の物の大きさの方に着目し、小さな容器からはみ出した部分を切り取って別の小さな容器を使って収めようという発想である()。

つまり、最上位ビットが 1 から始まるコード番号(128..255)に相当する文字が存在する場合には、それを暗号化エンコードして複数の 7 ビットの US-ASCII 文字列に一旦置き換え、送受信を行い、最終的に受信者側で復号する際に 8 ビットのコード番号に戻すというやり方で対処している。これが MIME の Quoted-Printable と呼ばれる暗号化方法である。

Quoted-Printable

最上位ビットが 1 から始まるコード番号(128..255)に相当する文字を一時的に退避するためのアルゴリズムである。簡単に言うと、コード番号を 2 文字の 16 進数の表記に置き換え、その頭に目印となる記号 "=" を付けた計 3 文字の US-ASCII 文字列に暗号化する()。これは、フランス語のような非英語の欧語圏の利用者だけでなく英語圏の利用者にとっても、最上位ビットが 0 から始まる US-ASCII のコード番号領域に対しても使えるので、本来は使用不可能な Email 規格上の予約語と重複する記号や制御文字([FF] = ページ送り、等)を利用できるようになるという利点がある。‘Printable’(印刷可能)という名前が付いているのは、そのためだろう。

このアルゴリズムは、本来は使うことのできない US-ASCII の英数字(と一部の記号)以外の特殊な文字を“とりあえず救済する”という非常的な手段を用意することに主眼がある。一方、一般的な文字(US-ASCII の英数字と一部の記号)はそのまま表記が可能である。すなわち、一般的な文字については 7 ビット用の表玄関をそのまま使い、特殊な文字については別の非常口(表玄関よりも 3 倍狭くて不便)を設けて、使い分けようというのである。これは US-ASCII と一般的な文字に関しては共通する欧語圏においては、特殊な文字を使う頻度は一般的な文字よりも少ないことが考えられるので、たとえ非常口が 3 倍狭くて不便であったとしても、表玄関の使い勝手が従来と変わらない方が都合が良いと考えられたからだろう。

Quoted-Printable では、一部の文字(一般的な文字)をそのまま無変換で使用し、一部の文字(特殊な文字)については暗号化して使い分けるという方式を採るので、暗号化された文字に関しては、3 倍の文字量となってしまう。使用頻度が少ないからだが、ともかく容量的な変換効率は悪い。最上位ビットが 1 から始まるコード番号(128..255)のデータが、通常の最上位ビットが 0 から始まるコード番号(0..127)のデータと均等な割合で出現するような場合は、この変換効率の悪さが響いてくる。例えば、本来は文字データではない(バイト単位で扱わない)画像ファイル等のバイナリデータを、便宜的に文字データとして扱う(バイト単位で扱う)Email 規格を使って送信しようとする場合、最上位ビットが 0 となるか 1 となるかは特に偏らずに均等な確率になるだろう。こういう 1/2 の確率で出現する最上位ビットが 1 から始まるコード番号(128..255)のデータを Quoted-Printable 方式ですべて 3 倍の数の文字に変換していたのでは、効率が非常に悪い。そこで、一般的な文字か特殊な文字かを区別せず、8 ビットのすべての領域の文字を根こそぎ 6 ビットの別のコード番号体系に置き換えてしまおうというのが、Base64 方式である。

Base64

まず、単純にバイナリデータを 8 ビットの文字列として表現する(当然、文字列としては無意味な文字の羅列となる)。この文字列を 3 文字セットにして考える。つまり、8 × 3 = 24 ビットのデータを単位として考える。このデータを 6 ビットを 1 バイトとする文字コード体系で考えれば、4 文字分のデータとみなせる。この 6 ビット体系の文字データを 7 ビット体系の US-ASCII の中の一部の文字(予約文字以外の使用可能な文字)に変換して割り付けるのである。US-ASCII のうち大文字小文字の英字に数字と "+"、"/" の記号を使って 6 ビット(0..63)のコード番号を表現する。つまり、8 ビット体系の文字 3 文字を 7 ビット体系の文字 4 文字に置き換えて表現することになるので、変換効率は、4/3 = 約 133% となる。

ちなみに、8 ビットの文字列として表現されたデータが、3 で割り切れない文字数の場合は、最後の文字列の組が、1 文字足りなくて 16 ビットだったり、2 文字足りなくて 8 ビットだったりするケースが考えられる。この場合は、足りない分を下位ビットに 0 を補って 24 ビットにして変換を行う。その上で、1 文字足らなかった場合は変換後の文字列に "=" を 1 つ、2 文字足らなかった場合には 2 つ、それぞれくっつけ、そのような 0 を下位ビットに補った印としておき、復元時の目安とすることになる。

(ISO-2022-JP を除く)多くの日本語文字コード体系も 8 ビットを 1 バイトとする体系をベースとしているので、MIME による 7 ビット化を行う必要がある。この場合、Quoted-Printable と Base64 が考えられるわけだが、欧語の場合と違って、最上位ビットが 0 か 1 かという確率に偏りがない点で、バイナリデータの特性に近い。つまり、変換方法としては Base64 が適している。そのことから、日本語は Base64 でエンコードすることが推奨されており、MUA(Mail User Agent: いわゆるメールソフト)も Base64 のインプリメントを主眼に置いてものと考えられる。

重ねて言うが、MIME は 8 ビットを 1 バイトとする文字コード体系を、7 ビットを 1 バイトとする文字コード体系である Email の基盤の中に無理矢理詰め込むための拡張方法であり、非英語の欧語圏や、16 ビットを 1 バイトとする UTF-8、EUC-JP、Shift_JIS にとって必要となるものである。それに対して、1 バイトを 7 ビットで考える思想をそのまま受け入れて 14 ビットの容器を新たに作ってそこに日本語のコード番号を置いてしまおうという ISO-2022-JP の場合には、MIME は必要がない。原則としては、そのまま使える。ただし問題となるのは、使用可能文字に制限が多いメールヘッダの場合である。メールヘッダの場合は、たとえ 7 ビットの US-ASCII であっても使用可能文字に制限があるので、ISO-2022-JP とてその例外ではないのである。

1 行の文字数制限

RFC 2822 では、データ転送上の限界として、1 行のデータの長さは最大で(US-ASCII で)1000 文字以内に限定するように定めている。行の終端区切りデリミタとなる改行コードは [CR][LF] のコントロール文字 2 文字の組合せで表されるので、それを除くと実際には最大 998 文字以内である。また、998 文字制限とは別に、推奨の 78 文字制限([CR][LF] を除く)もある。こちらは、MUA における表示上の問題に配慮したものである(RFC 2822 2.2.1)。

US-ASCII だけからなるテキストデータなら、単純に(78 文字以内の)一定の文字数で改行すればいいだけである。

8bit の非米・欧語で Quoted-Printable で暗号化エンコードしたいた場合はどうすればいいだろうか? 78 文字制限は、表示上の配慮であるので、復号デコードした後の、元の文章の文字数に対して考慮することになる。78 文字全部が Quoted-Printable エンコードが必要だったとしても、3 倍になるだけで、転送上の 998 文字制限を超えることは考えられない。

では、我々の日本語のような 2 バイト文字コードの場合はどうなるだろうか? 2 バイトで 1 文字とはいっても、元々日本語文字は全角文字として表現され、半角の英数文字 2 文字分で表示するのが普通である。すなわち、全角文字で考えた場合、1/2 の 39 文字として考えるべきだろう。またこの場合、転送上の 998 文字制限については、半角英数文字 78 文字の場合と同様に考えることができ、Quoted-Printable で最大 3 倍、Base64 で 4/3 倍になる程度だから、やはり 998 文字制限を超えることは考えられないことになる。

また、UTF-8 の場合、日本語は 3 バイト文字コードとして表現される。この場合は 2 バイト文字コードの状況にさらに 3/2 倍して考えることになるが、Base64 の 4/3 倍と併せて考えても全体で最大 2 倍ということになる(やはり 998 文字制限を超えることは考えられない)。

つまり、いずれにせよ、表示上の 78 文字制限を守っていれば、転送上の 998 文字制限については特に考慮しなくとも満たしていることになる。すなわち、表示上の 78 文字制限についてのみ考えればよい

ただし、前節で最後に述べたように、このように「単なる表示上の問題」として文字数制限を捉えればいいのは、結局メール本文に限定されてしまう。メールヘッダについては様々な制限が課されており、文字数制限についても、暗号化前の文字列ではなく、暗号化後の US-ASCII 文字列として見た場合にその制限文字数に納まることを要求される。

行の折り返し方法

メールヘッダの各領域fieldは、1 つの領域につき 1 行が論理的な原則だが、上述の文字数制限の存在により便宜的に複数行にわたって折り返すfoldingことができる。これを折り返し空白folding white spaceと呼び、通常の空白文字(WSP)と区別する。ちなみに、通常の空白文字は [SP]SPace[HTAB]Holizontal TAB のことであり、論理的な単一行の中において、構文上の要素lexical tokenと要素を区切るために使われるものである。それに対して、折り返し空白の場合は、その論理的単一行の中のいずれかの通常の空白文字の直前で [CR][LF] で折り返すことによって表現される。メールを受信した側においては、直後に空白文字が存在するような [CR][LF] を除去することで、折り返しを解除unfoldingし、本来の論理的単一行を再現することになる。(以上 RFC 2822 2.2.3)

以上のような仕様は、単語を複数個並べて文章を構成する欧語(特に英語)の特性を前提として定められたものである。例えば、アルファベットが 78 文字以上の長さで一度も空白文字で区切られない単一の長大な単語があるようなケースは想定されていない。なぜなら、空白文字が一つも存在しないので、[CR][LF] で折り返すことができないからである。また仮に、強引に [CR][LF] で改行しその直後に空白文字を挿入して「折り返し空白」を表現したとしても、受信時にとりあえず折り返しは解除されて論理的単一行としては復元されるが、挿入した空白文字もそのまま残ってしまい、元の文字列が壊れることになる(参考:インターネットメールの注意点ヘッダにおける日本語の扱いに関する問題)。

80 文字の a の連続した単語:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

とりあえず78文字で折り返したとする(便宜的に [SP] を挿入せざるを得ない):
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[CR][LF]
[SP]aa

受信側で復元した状態:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[SP]aa

日本語の文章の場合特に元々、必ず空白文字が文章の中に出現するというものではないので、生の日本語文字列を直接「折り返し空白」によって 1 行あたり 78 文字以内に折り返すという手法は不可能である。このため、RFC 2047 では、メッセージヘッダ中における非 US-ASCII 文字の暗号化方法について定義しており、複数の暗号化された文字列を並べた場合、その暗号化された文字列同士を区切るための空白文字は、復号時において除去されることが定義されている(RFC 2047 6.2)。すなわち、元の文章を RFC 2047 のやり方に従って暗号化して、その暗号化された文字列が 1 行あたり 78 文字以内に収まるように複数個に断片化し、「折り返し空白」の手法によって複数行にまたがって表現されるようにすればいいのである。

ちなみに、暗号化された文字列同士ではない、非暗号化文字列と暗号化文字列とを区切るための空白文字は、通常の単語間の区切りのための空白文字とみなされて受信時に除去はされないので、注意が必要である。

本来的に、RFC 2047 の MIME 暗号化ヘッダというものは、8 ビットデータを 7 ビット伝送路で安全に伝送するための拡張規定なので、上記のように、長い文字列を折り返すことを目的とするものではない。8 ビットメールシステムにおいては、MIME 暗号化を用いずとも、とりあえず安全に 8 ビットデータをヘッダに使用することも可能である。7 ビット環境を前提として規定されている RFC 2822 では、8 ビット目の領域(128..255)の文字の使用は明示的に許可されていないが、本来的には、制御文字と予約された特殊記号(specials)の使用を禁止してそれ以外の文字をすべて許しているので、8 ビット環境では自ずと 8 ビット目の領域(128..255)はすべて利用可能な文字となるはずである。すなわち 8 ビット環境下では、予約された特殊記号だけ quoted-string として表現すれば、MIME 暗号化せずとも、そのまま使うことも可能なのである。そんな状況で、長い文字列の折り返しのみの目的に、MIME 暗号化を使用するというのも、おかしな話である。これは RFC 2822 のヘッダ行の折り返し手法に不備があるからである。

例えば、空白は、[SP] もしくは [HTAB] のいずれか、という規定になっているが、このうち「[HTAB] を使った場合は、折り返しを解除した場合に削除される空白として扱う」というような規定にすれば、日本語の文章でも復元時に余計な空白が出現するというような不都合は生じなくなる。元々 [HTAB] は制御文字なのだし、欧語圏の人々でも、単語間の区切として [HTAB] を使う人は少ないと思われるから、RFC 2822 の折り返し空白の扱いについてこのような修正を行うことは、さしたる影響は与えないはずである。

80 文字の a の連続した単語:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

とりあえず78文字で折り返したとする(便宜的に [HTAB] を挿入せざるを得ない):
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[CR][LF]
[HTAB]aa

受信側で復元した状態([HTAB] は復元時に削除される):
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

参考

バイト
現在、1 バイト = 8 ビットが常識のように思われているが、バイトという単位は、一定数の複数ビットを一束にして扱う概念であり、必ずしも 8 ビットに限定されたものではなかった。システムによって様々で、4 ビットや 5 ビットのものも存在した。
制御文字
[HT](水平タブ。[TAB]、[HTAB] などと表記されることもある)を除いて、通常は表示不可能文字で、[NUL]、[SOH]、[STX]、[ETX]、[EOT]、[ENQ]、[ACK]、[BEL]、[BS]、[HT]、[LF]、[VT]、[FF]、[CR]、[SO]、[SI]、[DLE]、[DC1]、[DC2]、[DC3]、[DC4]、[NAK]、[SYN]、[ETB]、[CAN]、[EM]、[SUB]、[ESC]、[FS]、[GS]、[RS]、[US]、[DEL] の 33 文字がある。
文字データ通信
文字データ通信はバイト単位でデータを送受信することになる。バイナリデータ通信とは違うわけである。FTP においても、ASCII ファイル通信とバイナリファイル通信の 2 通りがあるのを思い出して欲しい。Email の規格がバイナリデータ通信を前提としていたのなら、このような問題は生じなかったわけだが、Email が文字データ通信を大前提としている規格だからこそ、1 バイトが 7 ビットか 8 ビットかの違いが大きな問題となるわけである。
16 ビットが 1 バイト
内部的には 8 ビット 1 バイトとして扱って文字データを処理することは事実であるが、一方、本来は 16 ビットで 1 つのコード番号として完成するので、例えば 1 バイト目と 2 バイト目の中間で行を折り返したような場合、日本語の文字データとしては整合性を失って壊れてしまう。つまり、本質的には 16 ビットを 1 バイトとして扱う文字コード体系と呼ぶのが正確であり、8 ビットを 1 バイトとして扱う文字コード体系のシステム上においても、2 バイト分の文字データとして「実用の上で通用する」というだけである。
2 バイト文字
このように 16 ビットを 1 バイトとして扱えば、ちょうど 8 の倍数なので、日本語や中国の文字コードに対応していない欧米圏の 8 ビットの文字コード体系のシステムでデータを受け取った場合でも、単に意味不明の文字が並んでいるだけで済み、想定外の問題が発生することはないと考えたからだろう。
アプローチの違い
7 ビットの小さな容器に 2 倍の 8 ビット分の多くの文字を詰め込もうとする MIME のアプローチに対し、ISO-2022-JP のアプローチは、容器が小さすぎるなら複数個横に並べてつぎ足せば大きな容器の代用になるという発想である。MIME の場合も結局は複数個の容器を使うことになるのは同じだが、ISO-2022-JP のように容器をくっつけて並べて別の新たな大きな容器を作るという発想ではない。容器に入れる物(文字)の方を複数にぶった切って複数の容器に分けて収納するという発想である。
Quoted-Printable の暗号化
ちょうど、HTML で予約文字の < や &、特殊文字の © などを表示する場合に、&#60; や &#38;、&#169; と表記するのに似ている。

《以上》