For Development HEAD DRAFTSearch (procedure/syntax/module):

12.51 rfc.mime - MIMEメッセージ処理

Module: rfc.mime

RFC2045からRFC2049で定義されている、 多目的インターネットメール拡張(Multipurpose Internet Mail Extensions; MIME) メッセージを扱う便利な手続きです。MIME特有のヘッダフィールドやメッセージボディを パーズしたり作成したりするAPIが提供されます。

このモジュールは主としてビルディングブロックとなる低レベルの手続きに フォーカスしており、アプリケーション特有のモジュールがこの上に 構築されることを意図しています。例えばrfc.httpはPOSTリクエストの ボディをmultipart/form-dataとして構築する際にこのモジュールを 利用します(rfc.http - HTTPクライアント参照)。

このモジュールは、rfc.822モジュールと一緒に使うことを 想定しています(rfc.822 - RFC822メッセージ形式参照)。

ヘッダフィールドのユーティリティ

MIME特有のヘッダフィールドをパーズしたり生成したりする便利な手続き。

Function: mime-parse-version field

{rfc.mime} fieldがそのMIMEバージョンのヘッダフィールドとして有効であれば、 そのメジャーバージョン番号とマイナーバージョン番号をリストにして 返します。そうでなければ、#fを返します。 fieldには#fを渡せるので、rfc822-header-refの 戻り値を直接渡すこともできます。rfc822-read-headersにより 返されるパーズ済みヘッダのリストを渡すことで、以下のように MIMEのバージョンを得ることができます。(現在は、(1 0)です。)

(mime-parse-version (rfc822-header-ref headers "mime-version"))

注意: fieldはトークンの間にコメントを含むかもしれないので、 #/\d+\.\d+/のような単純な正規表現では不十分です。

Function: mime-parse-content-type field

{rfc.mime} “content-type”ヘッダフィールドをパーズし、次のようなリストを 返します。

(type subtype (attribute . value) ...)

ここで、typesubtypeはそれぞれ、MIMEメディアタイプと サブタイプを文字列で表したものになります。

(mime-parse-content-type "text/html; charset=iso-2022-jp")
 ⇒ ("text" "html" ("charset" . "iso-2022-jp"))

fieldが有効なcontent-typeフィールドでない場合は、 #fが返ります。

Function: mime-parse-content-disposition field

{rfc.mime} RFC2183に定められたContent-Dispositionヘッダフィールドをパーズします。 (mime-parse-content-disposition "attachment; filename=genome.jpeg;\ modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";") ⇒ ("attachment" ("filename" . "genome.jpeg") ("modification-date" . "Wed, 12 Feb 1997 16:29:51 -0500"))

Function: mime-parse-parameters :optional iport
Function: mime-compose-parameters params :optional oport :key start-column

{rfc.mime} これらは、(RFC2045の5.1節にあるような)ヘッダフィールドの値のparameter 部分をパーズしたり作成したりするための低レベルのユーティリティ手続きです。

mime-parse-parametersはヘッダフィールドの値のパラメータ部分を 入力ポートiportから読んでパーズし、パラメータの名前と値の 連想リストを返します。 mime-compose-parametersはその逆で、連想リストをとり、 パラメータ部分を構成してoportへと書き出します。 iportoportはそれぞれ省略された場合、 current-input-portcurrent-output-portを デフォルトとします。また、oport#fを渡すと mime-compose-parametersは結果をポートに書き出すかわりに 文字列として返します。

(call-with-input-string
   "; name=foo; filename=\"foo/bar/baz\""
   mime-parse-parameters)
 ⇒ (("name" . "foo") ("filename" . "foo/bar/baz"))

(mime-compose-parameters
 '(("name" . "foo") ("filename" . "foo/bar/baz"))
 #f)
 ⇒ "; name=foo; filename=\"foo/bar/baz\""

mime-compose-parametersはヘッダ行が長くなりすぎる場合に パラメータ間に折り返し改行を入れようとします。パラメータ部分が始まる カラム数はstart-columnで与えることができます。

将来は、これらの手続きにRFC2231のパラメータ値拡張を透過的に処理させる予定です。

Function: mime-decode-word word

{rfc.mime} RFC2047でエンコードされたwordをデコードします。 wordがRFC2047でエンコードされたものでない場合は、そのまま 返されます。

この手続きはword全体がRFC2047の規定する“encoded-word”である場合にのみ デコードを行うことに注意してください。複数のエンコードされた部分や エンコードされていない部分が混ざっているフィールドを扱う場合は、 下に示すmime-decode-textを使います。

(mime-decode-word "=?iso-8859-1?q?this=20is=20some=20text?=")
 ⇒ "this is some text"
Function: mime-decode-text text

{rfc.mime} text中に含まれるすべてのencoded wordをデコードした文字列を返します。 この手続きは、エンコードされていない部分とエンコードされている部分が混ざっていたり、 複数のエンコードされている部分を持つヘッダフィールドボディを処理することが できます。そのようなフィールドの例はemailのSubjectフィールドです。

(mime-decode-text "Yamada Taro (=?utf-8?B?5bGx55SwIOWkqumDjg==?=)")
 ⇒ "Yamada Taro (山田 太郎)"

この手続きを「構造化された」ヘッダフィールドボディ (RFC2822 2.2.2節参照) に適用する際には注意が必要です。 構造化されたヘッダフィールドボディをパーズする正式な方法は、 最初にトークンに分割して、それから各wordを mime-decode-wordを使ってデコードするというものです。 なぜならデコード後のテキスト中に、パージングに影響を与える文字が含まれている かもしれないからです。 (ただし、単に参考情報を人間にわかりやすいように表示するだけの目的の場合は、 簡便のためにヘッダフィールド全体をこの手続きで一度にデコードしてしまっても 良いでしょう)。

Function: mime-encode-word word :key charset transfer-encoding

{rfc.mime} wordをRFC2047フォーマットにエンコードします。キーワード引数 charsetは文字列かシンボルで文字エンコーディングスキームを指定します。 デフォルトはutf-8です。charsetの指定がutf-8以外で、 wordが完全な文字列である場合は、 まずwordcharsetのエンコーディングへと変換され、 その上でトランスファーエンコーディングがかけられます。

(mime-encode-word "this is some text")
 ⇒ "=?utf-8?B?dGhpcyBpcyBzb21lIHRleHQ=?="

キーワード引数transfer-encodingは各オクテットを伝達上安全な 文字列へどエンコードする方法を指定します。サポートされている値は、 Base64を指定するシンボルbBbase64、 およびQuoted printableを指定する Qqquoted-printableです。 これ以外の値を渡した場合はエラーが通知されます。デフォルトはBase64です。

この手続きは結果のencoded wordの長さを気にしませんが、 RFC2047によればencoded wordは75オクテットまでに収めることが 要請されています。この要請に対応するには下に示す mime-encode-textを使って下さい。

(註:ほとんどのGaucheの手続きでは、キーワード引数encodingにより 文字エンコーディングを指定します。しかしこの手続きの文脈では 2つの「エンコーディング」が存在しているので、混乱を避けるために RFC文書で使われている“charset”および“transfer-encoding”の用語を 使うこととしました。)

Function: mime-encode-text text :key charset transfer-encoding line-width start-column force

{rfc.mime} textを、必要ならばRFC2047フォーマットに従いエンコードします。 また、結果が長すぎる場合の行の折り返しも考慮します。

キーワード引数charsettransfer-encodingの意味は mime-encode-wordと同じです。

もしwordが印字可能なASCII文字のみで構成されていた場合は エンコーディングは行われず、行の折り返しのみが処理されます。 但し、force引数に真の値が与えられた場合はASCIIのみのtextも エンコードされます。

line-widthは結果に現れる行の最大値を指定します。デフォルトは76です。 encoded wordがこれを越える場合は、複数のencoded wordへと結果は分割され、 間にCR LF SPCシーケンス(RFC2822で定義される“folding white space”)が挿入されます。 line-width#f0を渡すことで 行の折り返しを抑制することができます。 encoded wordには文字数でいくらかのオーバヘッドがあるため、 あまり小さいline-widthには意味がありません。現在の実装では 30以下の値は拒否されます。

start-columnキーワード引数は、ヘッダフィールド名を入れるために エンコード結果の最初の行だけを短くするのに使えます。 例えばSubjectヘッダフィールドのボディをエンコードする際に、 (string-length "Subject: ")の値を渡してやれば、 結果を直接"Subject: "の後に連結することができるわけです。 デフォルトの値は0です。

この手続きはstructured header fieldをエンコードするようには設計 されていません。structured header fieldには、どの部分がエンコード 可能でどの部分にfolding white spaceが挿入可能かについてさらなる 制約があるためです。安全な方法は、まず必要な部分をエンコードし、 それから折り返しを考慮しつつstructured header fieldを組み立てることです。

ストリーミングパーザ

メッセージ全体が読み込まれる前にメッセージボディをどのように 扱うかをコントロールできるように、ストリームパーザが用意されて います。

Function: mime-parse-message port headers handler

{rfc.mime} 基本的なストリームパーザです。portは、メッセージを読み込む 入力ポートです。headersrfc822-read-headersにより パーズされたヘッダのリストです。つまり、この手続きは、 portから読み込まれたメッセージのヘッダ部分がパーズされた 後に使われることを想定しています。

(let* ((headers (rfc822-read-headers port)))
  (if (mime-parse-version (rfc822-header-ref headers "mime-version"))
     ;; parse MIME message
     (mime-parse-message port headers handler)
     ;; retrieve a non-MIME body
     ...))

mime-parse-messageheadersを解析し、 メッセージボディのそれぞれについて、2引数をもって handlerを呼び出します。

(handler part-info xport)

part-infoは、以下で説明するような、メッセージのこのパートの 情報をカプセル化した<mime-part>ストラクチャです。

xportは入力ポートで、最初はメッセージボディの先頭を指しています。 ハンドラはこのポートからメッセージボディを読み込むことが出来ます。 xportはMIMEバウンダリを認識し、パートの最後に到達したら EOFを返します。 (元のportから直接読み込まないようにして下さい。 そうしてしまうと、vportの内部状態がおかしくなります)。

handlerは、パートをメモリに読み込んだり、ディスクに保存したり、 あるいはそのパートを無視したりできます。ただ、何をするにせよ、 vportがEOFを返すまでデータを読まなければなりません。

handlerの戻り値は、part-infocontentスロットに セットされます。

メッセージが、ネストしたマルチパートメッセージを含んでいる場合は、 handlerは深さ優先でそれぞれの“葉”のパートに対して呼ばれます。 handlerは、part-infoストラクチャを調べることで、 そのネストのレベルを知ることができます。

メッセージはマルチパートである必要はありません。メッセージが MIME messageタイプである場合は、handlerは囲まれたメッセージの ボディに対して呼ばれます。メッセージが、textapplication などの他のメディアタイプの場合は、handlerは単にメッセージボディに 対して呼ばれます。

Class: <mime-part>

{rfc.mime} MIMEパートのメタ情報を含むストラクチャです。 これは、そのパートのヘッダが読み込まれた時点で構築され、 そのパートのボディを読み込むハンドラに渡されます。

以下のスロットを持ちます。

Instance Variable of <mime-part>: type

MIMEメディアタイプの文字列。そのパートのcontent-typeヘッダが 省略された場合は、適切なデフォルト値がセットされます。

Instance Variable of <mime-part>: subtype

MIMEメディアのサブタイプの文字列。そのパートのcontent-type ヘッダが省略された場合は、適切なデフォルト値がセットされます。

Instance Variable of <mime-part>: parameters

content-typeヘッダフィールドに渡されるパラメータの連想リスト。

Instance Variable of <mime-part>: transfer-encoding

content-transfer-encodingヘッダフィールドの値。 このヘッダフィールドが省略された場合は、適切なデフォルト値が セットされます。

Instance Variable of <mime-part>: headers

rfc822-read-headersによりパーズされた、ヘッダフィールドのリスト。

Instance Variable of <mime-part>: parent

それがマルチパートメッセージあるいはカプセル化されたメッセージの パートである場合は、それを含んでいるパートの<mime-part> ストラクチャを指します。そうでなければ#fを返します。

Instance Variable of <mime-part>: index

同じ親を持つパートの中でのそのパートのシーケンス番号。

Instance Variable of <mime-part>: content

そのパートのメディアタイプがmultipart/*あるいはmessage/*で ある場合は、このスロットにはそれに含まれるパートのリストが 入っています。そうでなければ、handlerの戻り値が 格納されています。

Instance Variable of <mime-part>: source

このスロットはMIMEメッセージを作成する時のみ使われます。 呼び出し元は、このスロットにファイル名をセットすることで、 MIMEメッセージのこのパートにファイルの内容を挿入することができます。 詳しくは下のmime-compose-messageの項を参照してください。

Function: mime-retrieve-body part-info xport outp

{rfc.mime} メッセージボディを取得するための手続きです。 mime-parse-messageへ渡される、handlerの ビルディングブロックとなることを意図しています。

part-infoは、<mime-part>のオブジェクトです。 xportはハンドラに渡された入力ポートで、 そこからMIMEパートが読みこまれるものです。

この手続きは、xportからEOFに達するまで読み込み、 part-infotransfer-encodingも見て、 ボディを適切にデコードします。つまり、base64やquoted-printable のエンコーディングは適切に処理されます。結果が出力ポートoutpへと 出力されます。

この手続きは文字集合の変換は扱いません。 必要であれば、呼び出し側がoutpとしてCES変換ポートを 使う必要があります(gauche.charconv - 文字コード変換参照)。

典型的なケースのために、いくつかの便利な手続きがmime-retrieve-body の上に定義されています。

Function: mime-body->string part-info xport
Function: mime-body->file part-info xport filename

{rfc.mime} MIMEメッセージのボディを読み込み、転送(transfer)エンコーディングを デコードし、それぞれ文字列として返すか、ファイルへ書き出します。

MIMEメッセージパーザの最もシンプルな使い方は次のように なります。

(let ((headers (rfc822-read-headers port)))
  (mime-parse-message port headers
                      (cut mime-body->string <> <>)))

これは、メッセージの全てをメモリに読み込み、 一番上層の<mime-part>オブジェクトを返します。 (“葉”である<mime-part>オブジェクトのcontentフィールドは、 そのパートのボディを文字列として保持しています。) 内容の転送エンコーディング(content transfer encoding)は認識され処理 されますが、文字集合の変換は行われません。

メッセージボディを直接ファイルに書き出したり、MIMEメディアタイプや 他のヘッダ情報に基づいていくつかのボディをスキップしたいかもしれません。 その場合は、ロジックをハンドラのクロージャに入れることができます。 それが、このモジュールが、オールインワンの手続きではなく、 ビルディングブロックを提供している理由です。

メッセージの構築

Function: mime-compose-message parts :optional port :key boundary
Function: mime-compose-message-string parts :key boundary

{rfc.mime} MIMEマルチパートメッセージを構築します。 mime-compose-messageは結果をport(省略時は現在の出力ポート) に書き出します。mime-compose-message-stringは結果を文字列で返します。 バウンダリ文字列をboundary引数で渡すことができます。省略された場合は 新しいバウンダリ文字列が下のmime-make-boundaryによって作られます。

mime-compose-messageはバウンダリ文字列を、 mime-compose-message-stringは構築されたメッセージ文字列と バウンダリ文字列の二つの値を返します。

メッセージの内容はparts引数で与えます。これは 上で説明した<mime-part>のインスタンスのリストか、 もしくはパートを記述するリストのリストです。後者は簡便のためにサポートされていて、 内部的には<mime-part>のリストに変換されます。

partのリストの各要素は以下の形式を取ります。

<part>           : <mime-part> | <mime-part-desc>

<mime-part>      : an instance of the class <mime-part>

<mime-part-desc> : (<content-type> (<header> ...) <body>)
<content-type>   : (<type> <subtype> <header-param> ...)
<header-param>   : (<key> . <value>) ...
<header>         : (<header-name> <encoded-header-value>)
                 | (<header-name> (<header-value> <header-param> ...))
<body>           : a string
                 | (file <filename>)
                 | (subparts <part> ...)

<header>の最初の形式において、 <encoded-header-value>は、元の値がASCII以外の文字を含んでいる場合、 呼び出し側でRFC2047やRFC2231であらかじめエンコードしておかなければなりません。 二番目の形式では、自動的にRFC2231によるエンコードを行う予定がありますが、 今はまだ実装されていません。二番目の形式でエンコード済みの値を渡してしまうと、 将来のバージョンで二重にエンコードされる可能性があるので、 二番目の形式ではASCII文字のみを使ってください。

<body>が文字列の場合はそれがそのままパートの内容として使われます。 <body>(file filename)の場合は、 指定されたファイルが読み込まれてその内容が使われます。 <body>(subparts part …)の場合は、 ネストされたMIMEパートになります。

パートのcontent-typeと内容を一致させるのは呼び出し側の責任です。 例えば<body>がネストしたMIMEパートである場合は、 content-typeはmultipartでなければなりません。

アプリケーションによっては、content-transfer-encodingヘッダを適切に 与えることも重要です。content-transfer-encodingヘッダが与えられない場合、 内容はそのままMIMEメッセージ中に挿入されます。 アプリケーションによってはそれでも良いのですが、 例えばemailで送る場合は、バイナリの内容はbase64でエンコードするなどの配慮が必要です。

Function: mime-make-boundary

{rfc.mime} MIMEマルチパートメッセージのboundaryとして使えるユニークな文字列を返します。



For Development HEAD DRAFTSearch (procedure/syntax/module):
DRAFT