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

12.41 rfc.822 - RFC822メッセージ形式

Module: rfc.822

電子メールを交換する際に使用されるテキストのフォーマットである、“インターネット・ メッセージ・フォーマット”をパーズ/生成する手続きを定義しています。 最新の仕様は、RFC5322にあります。 このフォーマットは最初 RFC 822 で定義されたため、未だに“RFC822形式”と 呼ばれています。それがこのモジュール名の由来です。 以下では、このフォーマットを“RFC822形式”と呼びます。

メッセージヘッダのパーズ

Function: rfc822-read-headers iport :key strict? reader

{rfc.822} 入力ポート iport から、メッセージ・ヘッダの終わりに達するまで、 RFC822 形式のメッセージを読み込みます。 ヘッダ・フィールドは以下のフォーマットのリストに展開、分離されます。

((name body) ...)

Name … はフィールド名で、body … は対応するフィールドの ボディ、ともに文字列です。 フィールド名は小文字に変換されます。フィールドのボディは、行折り返しが 取り除かれる以外は変更されません。 フィールドの順番は保存されます。

デフォルトでは、パーザの動作は寛容です。ヘッダをパーズ中に EOF に 出会うとそれをメッセージの終端とみなします。継続(折り返し)行でもなく、 新しいヘッダフィールドの始端でもない行は無視します。このふるまいは キーワード引数 strict? に真の値を渡すことで変更することができます。 真を渡すと、このような不正な形式のヘッダに対してエラーを発生させるように なります。

キーワード引数 readeriport から一行読み込む手続きを とります。デフォルトは read-line です。ほとんどの場合これで 十分のはずです。

Function: rfc822-header->list iport :key strict? reader

{rfc.822} Deprecated. これはrfc822-read-headersの古い名前です。 互換性のために残してありますが、新しいコードは rfc822-read-headersを使って下さい。

Function: rfc822-header-ref header-list field-name :optional default

{rfc.822} rfc822-read-headers が返すパーズ済みのヘッダリストから 特定のフィールドを得るためのユーティリティ手続きです。

Field-name は小文字の文字列でフィールド名を指定します。 与えられた名前をもつフィールドが header-list 中にあれば、 その値を文字列で返します。そうでない場合、もし default が 与えられていればそれが返り、与えられていなければ #f が返されます。

もし同じフィールド名を持つヘッダエントリが複数あった場合は、 最初のエントリの値が返されます。複数ヘッダエントリの全ての値を取りたい場合は 下のrfc822-header-ref*を使ってください。

この手続きはrfc822-read-headersの結果だけでなく、 文字列をキーにしたリストのリスト ((name value option ...) ...) という構造からvalue部分を取り出すのに使えます。 例えばparse-cookie-stringの結果をrfc-822-header-refに渡せます。 (parse-cookie-stringについてはsee rfc.cookie - HTTPクッキー参照。)

(rfc822-header-ref
  '(("from" "foo@example.com") ("to" "bar@example.com"))
  "from")
 ⇒ "foo@example.com"

;; If no entry matches, #f is returned by default
(rfc822-header-ref
  '(("from" "foo@example.com") ("to" "bar@example.com"))
  "reply-to")
 ⇒ #f

;; You can give the default value for no-match case
(rfc822-header-ref
  '(("from" "foo@example.com") ("to" "bar@example.com"))
  "reply-to" 'none)
 ⇒ none

;; By giving the default value, you can distinguish
;; the no-match case and there's actually an entry with value #f.
(rfc822-header-ref
  '(("from" "foo@example.com") ("reply-to" #f))
  "reply-to" 'none)
 ⇒ #f
Function: rfc822-header-ref* header-list field-name

rfc822-header-refのようにheader-listから field-nameを持つヘッダを探しますが、最初に見つかったものだけでなく field-nameを持つ全てのヘッダの値をリストにして返します。 指定の名前を持つヘッダが無ければ空リストが返されます。

Function: rfc822-header-put header-list field-name field-value

RFC822ヘッダリストheader-listに、名前field-nameと値 field-valueを持つヘッダを追加したヘッダリストを返します。 field-nameは全て小文字に変換されます。 header-listが既に同名のヘッダを持っていた場合、それらは全て出力から除かれます。 引数に渡したheader-list自体は変更されません。

基本的なフィールドパーザ

RFC2822メッセージの「構造化」されたヘッダフィールドをパーズするために、 いくつかの手続きが提供されています。これらの手続きはヘッダフィールドの 本体部を処理します。たとえば、ヘッダフィールドが、 "To: Wandering Schemer <schemer@example.com>" であれば、これらの 手続きは "Wandering Schemer <schemer@example.com>" をパーズします。

ほとんどの手続きは入力ポートを引数にとります。通常は最初に、ヘッダフィールド 全部を rfc822-read-headers でパーズし、ヘッダの本体を rfc822-header-ref で取得してから、その本体用に入力文字列ポートを オープンして、それをこれらの手続きを用いてパーズします。

このように複雑になっているのは、フィールドのタイプによって別々の トークン化スキームが必要になるからです。RFC2822 では多くの場合 トークン間にコメントがあらわれことを許しているので、初心な正規表現では うまくいきません。RFC2822 のコメントはネスト可能で、正規表現では表現 しきれないからです。 そういうわけで、このレイヤの手続きは、いろいろな構文に対応できるよう 十分な柔軟性があるように設計されています。標準的なタイプのヘッダについては 高水準のパーザも提供されています。後述の「特定フィールド用パーザ」を 参照してください。

Function: rfc822-next-token iport :optional tokenizer-specs

{rfc.822} 基本的なトークナイザです。まず、もしあれば、白空白および/または コメント (CFWS) を iport から読み飛ばします。それから、 tokenizer-specs にしたがってトークンをひとつ読み込みます。 トークンを読み込む前に、iport が EOF に到達したら、EOF が 返されます。

tokenizer-specs はトークナイザ仕様のリストです。 トークナイザ仕様は、文字集合または文字集合と手続きのペアのどちらかです。

CFWS を読み飛ばしたあと、この手続きは iport の先頭の一文字 を見て、tokenizer-specs のひとつひとつに対してチェックします。 その文字が含まれている文字集合がみつかれば、トークンを次のようにして 引き出します。トークナイザ仕様が文字集合だけの場合、その文字集合に 属している文字の並びがトークンを構成します。トークナイザ仕様が文字集合と 手続きのペアだったら、その手続きを iport とともに呼びだし、 トークンを読み込みます。

もし、先頭の文字がどの文字集合ともマッチしなければ、その文字が iport から取り出され、それが返されます。

デフォルトの tokenizer-specs は以下のようになっています。

(list (cons #["] rfc822-quoted-string)
      (cons *rfc822-atext-chars* rfc822-dot-atom))

ここで rfc822-quoted-string および rfc822-dot-atom は 後述するトークナイザ手続きで、*rfc822-atext-chars* は RFC2822 で 規定された atext の文字集合に束縛されています。 つまり、rfc822-next-token はデフォルトでは RFC2822 で規定された quoted-string あるいは dot-atom のトークンを引き出します。

tokenizer-specs をつかって、ヘッダフィールドのパーズ方法を カスタマイズすることができます。たとえば、(1) 英字で構成された単語、または (2) クウォート文字列、のトークンを取り出したいときには、 rfc822-next-token をこんなふうに呼べます。

(rfc822-next-token iport
   `(#[[:alpha:]] (#["] . ,rfc822-quoted-string)))
Function: rfc822-field->tokens field :optional tokenizer-specs

{rfc.822} これは便利関数です。フィールド本体 field に対応する入力文字列ポート を生成し、それに対して、rfc822-next-token を全入力を消費するまで、 繰り返しよび、トークンのリストを返します。Tokenizer-specs は、 rfc822-next-token に渡されます。

Function: rfc822-skip-cfws iport

{rfc.822} iport から、すべてのコメントおよび/または白空白文字を消費し、 白空白でもコメントでもない、先頭の文字を返します。返された文字は、 iportに残ります。

Constant: *rfc822-atext-chars*

{rfc.822} atom を構成する有効な文字集合に束縛されています。

Constant: *rfc822-standard-tokenizers*

{rfc.822} デフォルトの tokenizer-specs に束縛されています。

Function: rfc822-atom iport
Function: rfc822-dot-atom iport
Function: rfc822-quoted-string iport

{rfc.822} それぞれ、atomdot-atom および quoted-string に 対応するトークナイザです。quoted-string 中の二重引用符および エスケープのためのバックスラッシュは rfc822-quoted-string に よって取り除かれます。

特定フィールド用パーザ

Function: rfc822-parse-date string

{rfc.822} RFC822 形式の日付文字列を取り、8つの値を返します。

year, month, day-of-month, hour, minutes, seconds, timezone,
day-of-week.

timezone は UT(グリニッジ標準時)からの分単位のオフセットです。 day-of-week は日曜日から数えた曜日で、情報が不足している場合は #f です。 monthは1から12までの整数です。 文字列がパーズ不可能ならば、全ての要素が #f になります。

Function: rfc822-date->date string

{rfc.822} RFC822形式の日付フォーマットをパーズし、SRFI-19 の <date> オブジェクト (日付 参照) を返します。string がパーズできないときは かわりに #f を返します。

SRFI-19の日付からRFC822形式の日付文字列を作成するには、 後で述べるdate->rfc822-dateが使えます。

メッセージの構築

Function: rfc822-write-headers headers :key output continue check

{rfc.822} これはrfc822-read-headersの一種の逆関数と言えます。 各ヘッダデータが(<name> <body>)の形になっているリストを受け取り、 outputキーワード引数で指定されるポートにRFC822形式で書き出します。 outputが省略された場合は現在の出力ポートが使われます。

デフォルトでは、この手続きはheaderで全てのヘッダが完結するものと みなし、最後にヘッダ部分の終了を示す空行を出力します。 ただしcontinueキーワード引数に真の値を渡すとこの動作は抑制され、 後からヘッダを追加で出力することができます。

上で「一種の」と書きましたが、それはこの手続きが完全な逆関数ではないからです 特に、ヘッダ行が長い場合の「行の折り返し」は呼び出し側でやっておかねばなりません。 RFC2822ではヘッダ行の絶対的な長さを998オクテットに制限しています。 データがそれを越える場合は適宜改行(\r\n)+空白文字を挿入してください。 これは自動ではできません。ヘッダデータのどこに改行を挿入できるかは 各ヘッダのセマンティクスに依存するからです。

また、ヘッダフィールドボディはNUL文字以外のASCII文字のみで構成されている 必要があります。それ以外の文字を含めたい場合は、MIME等適切なプロトコルを 使ってあらかじめエンコードしておいてください。 rfc.mimeモジュールのmime-encode-textが便利でしょう (rfc.mime - MIMEメッセージ処理)。 これもまた、ヘッダフィールド毎のセマンティクスに依存するため、 自動で行うことができません。

しかしこの手続きは、渡されたデータがRFC2822に沿っているかどうかをチェック できます。デフォルトではRFC2822に違反していればエラーが投げられます。 checkキーワード引数でこの振る舞いを変えられます。 可能な値は次のとおりです。

:error

デフォルト。違反があればエラーを投げます。

#f, :ignore

呼び出し側を信頼し、チェックを行いません。

procedure

rfc822-write-headersが違反を検出したら、 この手続きを3つの引数で呼び出します。 ヘッダフィールド名、ヘッダフィールドボディ、 そして違反の種類を示す以下のシンボルです。 この手続きは、修正されたヘッダフィールド名とヘッダフィールドボディの ふたつの値を返さなければなりません。 もしこの手続きがヘッダフィールド名とヘッダフィールドボディを修正せずに 返したら、:errorを指定したときのようにエラーが投げられます。

check引数に手続きを渡した際に、第3引数に渡されるシンボルは以下のとおりです。 将来のバージョンでは新たなシンボルが追加されるかもしれません。

incomplete-string

不完全な文字列が渡された

bad-character

NUL文字または非ASCII文字が含まれている

line-too-long

1行が998オクテットの制限を越えている

stray-crlf

CRLFの組み合わせでない、CRのみまたはLFのみが含まれている。

Function: date->rfc822-date date

{rfc.822} SRFI-19の<date>オブジェクト(日付参照) を取り、そのrfc822日付形式表現の文字列を返します。 rfc822-date->dateの逆関数です。



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