rfc.mime
- 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特有のヘッダフィールドをパーズしたり生成したりする便利な手続き。
{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+/
のような単純な正規表現では不十分です。
{rfc.mime
}
“content-type”ヘッダフィールドをパーズし、次のようなリストを
返します。
(type subtype (attribute . value) ...)
ここで、typeとsubtypeはそれぞれ、MIMEメディアタイプと サブタイプを文字列で表したものになります。
(mime-parse-content-type "text/html; charset=iso-2022-jp") ⇒ ("text" "html" ("charset" . "iso-2022-jp"))
fieldが有効なcontent-typeフィールドでない場合は、
#f
が返ります。
{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"))
{rfc.mime
}
これらは、(RFC2045の5.1節にあるような)ヘッダフィールドの値のparameter
部分をパーズしたり作成したりするための低レベルのユーティリティ手続きです。
mime-parse-parameters
はヘッダフィールドの値のパラメータ部分を
入力ポートiportから読んでパーズし、パラメータの名前と値の
連想リストを返します。
mime-compose-parameters
はその逆で、連想リストをとり、
パラメータ部分を構成してoportへと書き出します。
iport、oportはそれぞれ省略された場合、
current-input-port
とcurrent-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のパラメータ値拡張を透過的に処理させる予定です。
{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"
{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
を使ってデコードするというものです。
なぜならデコード後のテキスト中に、パージングに影響を与える文字が含まれている
かもしれないからです。
(ただし、単に参考情報を人間にわかりやすいように表示するだけの目的の場合は、
簡便のためにヘッダフィールド全体をこの手続きで一度にデコードしてしまっても
良いでしょう)。
{rfc.mime
}
wordをRFC2047フォーマットにエンコードします。キーワード引数
charsetは文字列かシンボルで文字エンコーディングスキームを指定します。
デフォルトはutf-8
です。charsetの指定がutf-8
以外で、
wordが完全な文字列である場合は、
まずwordがcharsetのエンコーディングへと変換され、
その上でトランスファーエンコーディングがかけられます。
(mime-encode-word "this is some text") ⇒ "=?utf-8?B?dGhpcyBpcyBzb21lIHRleHQ=?="
キーワード引数transfer-encodingは各オクテットを伝達上安全な
文字列へどエンコードする方法を指定します。サポートされている値は、
Base64を指定するシンボルb
、B
、base64
、
およびQuoted printableを指定する
Q
、q
、quoted-printable
です。
これ以外の値を渡した場合はエラーが通知されます。デフォルトはBase64です。
この手続きは結果のencoded wordの長さを気にしませんが、
RFC2047によればencoded wordは75オクテットまでに収めることが
要請されています。この要請に対応するには下に示す
mime-encode-text
を使って下さい。
(註:ほとんどのGaucheの手続きでは、キーワード引数encoding
により
文字エンコーディングを指定します。しかしこの手続きの文脈では
2つの「エンコーディング」が存在しているので、混乱を避けるために
RFC文書で使われている“charset”および“transfer-encoding”の用語を
使うこととしました。)
{rfc.mime
}
textを、必要ならばRFC2047フォーマットに従いエンコードします。
また、結果が長すぎる場合の行の折り返しも考慮します。
キーワード引数charsetとtransfer-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に#f
か0
を渡すことで
行の折り返しを抑制することができます。
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を組み立てることです。
メッセージ全体が読み込まれる前にメッセージボディをどのように 扱うかをコントロールできるように、ストリームパーザが用意されて います。
{rfc.mime
}
基本的なストリームパーザです。portは、メッセージを読み込む
入力ポートです。headersはrfc822-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-message
はheadersを解析し、
メッセージボディのそれぞれについて、2引数をもって
handlerを呼び出します。
(handler part-info xport)
part-infoは、以下で説明するような、メッセージのこのパートの
情報をカプセル化した<mime-part>
ストラクチャです。
xportは入力ポートで、最初はメッセージボディの先頭を指しています。 ハンドラはこのポートからメッセージボディを読み込むことが出来ます。 xportはMIMEバウンダリを認識し、パートの最後に到達したら EOFを返します。 (元のportから直接読み込まないようにして下さい。 そうしてしまうと、vportの内部状態がおかしくなります)。
handlerは、パートをメモリに読み込んだり、ディスクに保存したり、 あるいはそのパートを無視したりできます。ただ、何をするにせよ、 vportがEOFを返すまでデータを読まなければなりません。
handlerの戻り値は、part-infoのcontent
スロットに
セットされます。
メッセージが、ネストしたマルチパートメッセージを含んでいる場合は、 handlerは深さ優先でそれぞれの“葉”のパートに対して呼ばれます。 handlerは、part-infoストラクチャを調べることで、 そのネストのレベルを知ることができます。
メッセージはマルチパートである必要はありません。メッセージが
MIME message
タイプである場合は、handlerは囲まれたメッセージの
ボディに対して呼ばれます。メッセージが、text
やapplication
などの他のメディアタイプの場合は、handlerは単にメッセージボディに
対して呼ばれます。
{rfc.mime
}
MIMEパートのメタ情報を含むストラクチャです。
これは、そのパートのヘッダが読み込まれた時点で構築され、
そのパートのボディを読み込むハンドラに渡されます。
以下のスロットを持ちます。
<mime-part>
: type ¶MIMEメディアタイプの文字列。そのパートのcontent-type
ヘッダが
省略された場合は、適切なデフォルト値がセットされます。
<mime-part>
: subtype ¶MIMEメディアのサブタイプの文字列。そのパートのcontent-type
ヘッダが省略された場合は、適切なデフォルト値がセットされます。
<mime-part>
: parameters ¶content-type
ヘッダフィールドに渡されるパラメータの連想リスト。
<mime-part>
: transfer-encoding ¶content-transfer-encoding
ヘッダフィールドの値。
このヘッダフィールドが省略された場合は、適切なデフォルト値が
セットされます。
<mime-part>
: headers ¶rfc822-read-headers
によりパーズされた、ヘッダフィールドのリスト。
<mime-part>
: parent ¶それがマルチパートメッセージあるいはカプセル化されたメッセージの
パートである場合は、それを含んでいるパートの<mime-part>
ストラクチャを指します。そうでなければ#f
を返します。
<mime-part>
: index ¶同じ親を持つパートの中でのそのパートのシーケンス番号。
<mime-part>
: content ¶そのパートのメディアタイプがmultipart/*あるいはmessage/*で ある場合は、このスロットにはそれに含まれるパートのリストが 入っています。そうでなければ、handlerの戻り値が 格納されています。
<mime-part>
: source ¶このスロットはMIMEメッセージを作成する時のみ使われます。
呼び出し元は、このスロットにファイル名をセットすることで、
MIMEメッセージのこのパートにファイルの内容を挿入することができます。
詳しくは下のmime-compose-message
の項を参照してください。
{rfc.mime
}
メッセージボディを取得するための手続きです。
mime-parse-message
へ渡される、handlerの
ビルディングブロックとなることを意図しています。
part-infoは、<mime-part>
のオブジェクトです。
xportはハンドラに渡された入力ポートで、
そこからMIMEパートが読みこまれるものです。
この手続きは、xportからEOFに達するまで読み込み、
part-infoのtransfer-encoding
も見て、
ボディを適切にデコードします。つまり、base64やquoted-printable
のエンコーディングは適切に処理されます。結果が出力ポートoutpへと
出力されます。
この手続きは文字集合の変換は扱いません。
必要であれば、呼び出し側がoutpとしてCES変換ポートを
使う必要があります(gauche.charconv
- 文字コード変換参照)。
典型的なケースのために、いくつかの便利な手続きがmime-retrieve-body
の上に定義されています。
{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メディアタイプや 他のヘッダ情報に基づいていくつかのボディをスキップしたいかもしれません。 その場合は、ロジックをハンドラのクロージャに入れることができます。 それが、このモジュールが、オールインワンの手続きではなく、 ビルディングブロックを提供している理由です。
{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でエンコードするなどの配慮が必要です。
{rfc.mime
}
MIMEマルチパートメッセージのboundaryとして使えるユニークな文字列を返します。