[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

11.23 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メッセージ形式参照)。

Utilities for header fields

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

Function: mime-parse-version field

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

“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

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

これらは、(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

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

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 &keyword charset transfer-encoding

wordをRFC2047フォーマットにエンコードします。キーワード引数 charsetは文字列かシンボルで文字エンコーディングスキームを指定します。 デフォルトはutf-8です。charsetの指定がGaucheの 内部文字エンコーディングと異なっており、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 &keyword charset transfer-encoding line-width start-column force

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を組み立てることです。

Streaming parser

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

Function: mime-parse-message port headers handler

基本的なストリームパーザです。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 mesasgeタイプである場合は、handlerは囲まれたメッセージの ボディに対して呼ばれます。メッセージが、textapplication などの他のメディアタイプの場合は、handlerは単にメッセージボディに 対して呼ばれます。

Class: <mime-part>

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

メッセージボディを取得するための手続きです。 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

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

Message composer

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

Composes a MIME multipart message. Mime-compose-message emits the result to an output port port, whose default is the current output port. Mime-compose-message-string makes the result into a string. You can give a boundary string via boundary argument; when omitted, a fresh boundary string is automatically generated by mime-make-boundary below.

Mime-compose-message returns the boundary string. Mime-compose-message-string returns two values, the result string and the boundary string.

The content of the message is provided by the parts argument, which can be a list of instances of <mime-part> (see above) or lists that describe parts. The list form is supported for the caller's convenience, and internally it is converted to a list of <mime-part>s.

The syntax of each part element in parts are defined as follow.

 
<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> ...)

Note: In the first form of <header>, <encoded-header-value> must already be encoded using RFC2047 or RFC2231 if the original value contains non-ascii characters. In the second form, we plan to do RFC2231 encoding on behalf of the caller; but the current version does not implement it. The caller should not pass encoded words in this form, since it may result double-encoding when we implement the auto encoding feature; for the time being, the second form restricts ASCII-only values.

If <body> is a string, it is used as the part's content. If <body> is (file filename), the content is read from the named file. If <body> is (subparts part …), the part becomes nested MIME part.

It is the caller's responsibility to give the proper content. For example, if <body> is in the third form, the part must have multipart content type.

The caller needs to provide proper content-transfer-encoding header, depending on the application. If none is given, the content is inserted into the message as is, which may be appropriate for some applications, but if you want to use the result in email message you certainly want to encode binary part with base64, for example.

Function: mime-make-boundary

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


[ < ] [ > ]   [ << ] [ Up ] [ >> ]

This document was generated by Shiro Kawai on November, 22 2009 using texi2html 1.78.