Gauche:rfc.mime:rfc2047

Gauche:rfc.mime:rfc2047

MIMEのヘッダフィールドエンコーディング(RFC:2047)をちゃんと書こうとしたら意外に面倒だったのでメモ。

やること

文字列、文字エンコーディング(charset)、transfer encoding (enc)が 与えられた時に、encoded-word:

 =?charset?enc?encoded-text?=

を生成する。encはB(base64)かQ(quoted-printable)。

制約

encoded-wordは75文字以下 (rfc2047, section2)。 しかし、これがヘッダフィールドのボディに現れた場合、ヘッダフィールド名とコロンの部分が付け加わるので、 rfc2822の行の長さのrecommendation (78) を満たそうとするとencoded-wordの 長さの限界はもっと小さくなる (もっとも、ヘッダフィールド名とコロンの 後にすぐCRLF SPCを入れて連続行にすれば、ぎりぎりの長さのencoded-wordでも 何とかなる。)

encoded-wordがこれを越える場合は行折り返し(rfc2822 2.2.3, rfc2047 2)を 入れることになるのだが、そのように分割する場合、 それぞれのencoded-textはデコードした時にちゃんと完結した文字列に なるようにしなくちゃならない。例えばiso-2022-jpでエンコードする場合、 ひとかたまりのencoded-textをデコードしたら、US-ASCIIのステートから 始まって最後にUS-ASCIIのステートに戻るようになってないとだめ (rfc2047 3)。

例えば"こんにちは、世界" をそのままISO-2022-JPにすると

 ESC $ B $ 3 $ s $ K $ A $ O ! " @ $ 3 & ESC ( B

base64でエンコードすればencoded-wordは:

 =?ISO-2022-JP?B?GyRCJDMkcyRLJEEkTyEiQCQzJhsoQg==?=

もしこれが長すぎた場合、

 ESC $ B $ 3 $ s $ K $ A

 $ O ! " @ $ 3 & ESC ( B

とに分割してencoded-wordにするのはだめで、

 ESC $ B $ 3 $ s $ K $ A ESC ( B
 ESC $ B $ O ! " @ $ 3 & ESC ( B

ってな具合にそれぞれを完結した断片にした後でencoded-wordにする必要がある。

ということは、文字コード変換をかけてから適度に分割するのではなく、

  1. 何らかのヒューリスティクスを使って元の文字列を分割
  2. それぞれを文字コード変換して、さらにencoded-wordに
  3. その時点ではみ出してたら、1.に戻って別の分割を試す

としないとならない。で、ステップ2はそれなりに時間を喰うから なるべく手戻りを避けて最小限の繰り返しでそれなりに良い分割を見つけたい。 これを一般的に実装するのは結構めんどい。

header field unfolding

ちょっと疑問が出てきたのでメモ。

ヘッダフィールドボディは、CRLFを入れて次の行を空白で始めることで 行を折り返すことができる (folding)。この時挿入されるCRLFおよび 周囲の空白文字はまとめてFWS (folding white space) と呼ばれる (RFC:2822 3.2.3)。

  FWS             =       ([*WSP CRLF] 1*WSP)

folding white spaceはヘッダフィールドをパーズする前に取り除かれる (unfolding)。 RFC:2822 2.2.3:

The process of moving from this folded multiple-line representation
of a header field to its single line representation is called
"unfolding". Unfolding is accomplished by simply removing any CRLF
that is immediately followed by WSP. 

3.2.3:

That is to say, any CRLF that appears in
FWS is semantically "invisible."
Runs of FWS, comment or CFWS that occur between lexical tokens in a
structured field header are semantically interpreted as a single
space character.

何が疑問かというと、unfoldingにおいてFWS内のCRLFを取り除いた後の WSPの扱い。structured header fieldでは上に引用したように それは単一の空白文字と同じになり、パージングの際に単なる区切り以上の 意味を持たない。

けれど、Subjectみたいなunstructured fieldではどう扱うべきかが見当たらない。 2.2.3の例では:

For example, the header field:

       Subject: This is a test

can be represented as:

       Subject: This
        is a test

とあるので、単一の空白文字にすることが想定されているようだけれど、 そうなるとRFC:2047の要請で複数に分割されたencoded-word:

Subject: =?iso-2022-jp-2?B?VEVTVDobJEEkOCRlJDIkYCQ4JGUkMhsoQg==?=
 =?iso-2022-jp-2?B?GyRBJGAkNCQzJCYkTiQ5JGokLSRsJCskJCQ4JGMbKEI=?=

これのFWSを空白にすべきかどうかが問題になる。エンコード前の文字列には 空白は入っていないのだが、ここのFWSを空白にしてしまうとデコード後には その空白が現れてしまう。

mewでちょっと試してみたら、前後がエンコードされてない英文の時は空白に、 エンコードされてる日本語の場合は空白無しにデコードしてるみたいだな。 経験的に判断してるのかな?

ふーむ、ぐぐってみるとやはり問題視されているようだ。 ある種のヘッダフィールドボディのパラメータ部に関してはRFC:2231でもって FWSの空白問題は回避されている。けれどunstructuredなヘッダフィールドには 使えない。

むーこれは深追いすると泥沼っぽいなあ。程々にしとこう。

More ...