RFC 3875 The Common Gateway Interface (CGI) メモ - JUNNETWORKS/42-webserv GitHub Wiki
RFC 3875 - The Common Gateway Interface (CGI) Version 1.1 日本語訳 メモ
webservに必要そうな箇所のみ抜粋。 すべての仕様が書いてあるわけではないので注意。
-
サーバーはスクリプトとの間のゲートウェイとして動作する。 サーバーはクライアントからのリクエストを受け取り、そのリクエストによって適切なスクリプトを実行する。 サーバーはクライアントからのリクエストをCGIリクエストに変換し、スクリプトを実行し、スクリプトからのCGI応答をクライアントへのレスポンスへと変換する。
-
サーバーはクライアントからのリクエストによって提供されたパスによって実行するCGIを選択する。
-
サーバー側でエラー(通信周りなど)が発生した場合、サーバーは警告なしにスクリプトの実行を中断または終了させることができる。
-
Script-URI というものを定義する。
script-URI = <scheme> "://" <server-name> ":" <server-port>
<script-path> <extra-path> "?" <query-string>
- リクエストに関する情報はリクエストメタ変数とメッセージボディがある。
- メタ変数はケースインセンシティブ(大文字小文字区別無)で、サーバーからリクエストに渡されます。RFC3875では変数名は大文字とアンダーバーで表す。 メタ変数の渡し方はシステムの定義によりますが、多くのシステム(UNIX)ではメタ変数と同じ名前の環境変数によって渡されます(7.2参照)。
meta-variable-name = "AUTH_TYPE" | "CONTENT_LENGTH" |
"CONTENT_TYPE" | "GATEWAY_INTERFACE" |
"PATH_INFO" | "PATH_TRANSLATED" |
"QUERY_STRING" | "REMOTE_ADDR" |
"REMOTE_HOST" | "REMOTE_IDENT" |
"REMOTE_USER" | "REQUEST_METHOD" |
"SCRIPT_NAME" | "SERVER_NAME" |
"SERVER_PORT" | "SERVER_PROTOCOL" |
"SERVER_SOFTWARE" | scheme |
protocol-var-name | extension-var-name
protocol-var-name = ( protocol | scheme ) "_" var-name
scheme = alpha *( alpha | digit | "+" | "-" | "." )
var-name = token
extension-var-name = token
- これら以外の追加の実装定義のメタ変数には
X_
のプレフィックスを付ける必要がある。 - メタ変数の値としてゼロとNULLを区別できない。例えば
http://host/script
とhttp://host/script
はどちらもQUERY_STRING=
になる(NULL扱い)。 - 値がNULLの場合はオプションのメタ変数を省略(未設定)することができます。
- メタ変数の値は大文字小文字の区別あり。
以下実装する必要の有りそうなメタ変数のみ書く。全メタ変数が見たい場合は4.1参照。
CONTENT_LENGTH = "" | 1*digit
- 10進数でメッセージボディのサイズを記述する。リクエストにメッセージボディがない場合は設定しなくても良い。メッセージボディのサイズは transfer-encoding や content-codings を解除したサイズである必要がある。
CONTENT_TYPE = "" | media-type
media-type = type "/" subtype *( ";" parameter )
type = token
subtype = token
parameter = attribute "=" value
attribute = token
value = token | quoted-string
- リクエストにメッセージボディが含まれている場合はこのメタ変数がセットされる。値は MIME のものが用いられる。
- この変数にはデフォルト値はない。設定されてなくてもよいけど、設定されていない場合、スクリプトはエラーを返すかも。
- 各メディアタイプは文字コードを必須パラメータとして持っている。これは以下のルールによって定められる。
- メディアタイプに対してシステム定義のデフォルトの文字コードがある。
- text メディアタイプのデフォルト文字コードは ISO-8859-1
- メディアタイプの仕様で文字コードが定義されている。
- デフォルトの文字コードは US-ASCII である。
- HTTP Content-Type がリクエストに設定されている場合は必ずこの変数に値をセットする必要がある。
- リクエストボディがあったが、Content-Type がなかった場合、サーバーは正しい Content-Type を決定する努力をしなければならない。さもなければこのメタ変数を省略する必要がある。
- スクリプトと通信するためのCGIバージョンを定める。
- 必ずセットしないといけない?
The GATEWAY_INTERFACE variable MUST be set to the dialect of CGI being used by the server to communicate with the script.
- このRFCは
CGI/1.1
について述べているので、CGI/1.1
をセットしておけばよさそう。
PATH_INFO = "" | ( "/" path )
path = lsegment *( "/" lsegment )
lsegment = *lchar
lchar = <any TEXT or CTL except "/">
- CGI スクリプトによって解釈されるべきパスを設定する。
- リクエストのパスの情報から派生して設定される。 例えば
/hoge/
以下へのアクセスをとあるCGIに任せたい場合は/hoge/fuga/a
が来たらPATH_INFO=/fuga/a
のように設定するイメージ? - URLエンコードは解除されいなければならない。
- 大文字小文字は区別するし、サーバーはちゃんと大文字小文字区別したままこの変数に設定しないといけない。
- サーバーはPATH_INFOに許可する値に制限や制約を課しても良い。好ましくない場合はエラーとしてリクエストを拒否しても良い。
- ASCII以外の文字の扱いはシステム定義。
PATH_INFO の値を元にローカルのパスへのマッピングを行うための変数。
例
- リクエストのパスが
http://somehost.com/cgi-bin/somescript/this%2eis%2epath%3binfo
だとする。 -
PATH_INFO=/this.is.the.path;info
こうなる。 - サーバ設定で
root=/usr/local/www/htdocs/
となっているのなら、PATH_TRANSLATED=/usr/local/www/htdocs/this.is.the.path;info
となる。
この変数にはパーセントエンコーディングされたパラメータ文字列が含まれます。
Script-URI でいう <query_string> の部分がこの変数にセットされる。
サーバーはこの変数を設定する必要がある。URLパラメータが含まれていない場合は QUERY_STRING=
のように空文字列である必要がある。
REMOTE_ADDR = hostnumber
hostnumber = ipv4-address | ipv6-address
ipv4-address = 1*3digit "." 1*3digit "." 1*3digit "." 1*3digit
ipv6-address = hexpart [ ":" ipv4-address ]
hexpart = hexseq | ( [ hexseq ] "::" [ hexseq ] )
hexseq = 1*4hex *( ":" 1*4hex )
クライントのネットワークアドレス。
REMOTE_HOST = "" | hostname | hostnumber
hostname = *( domainlabel "." ) toplabel [ "." ]
domainlabel = alphanum [ *alphahypdigit alphanum ]
toplabel = alpha [ *alphahypdigit alphanum ]
alphahypdigit = alphanum | "-"
完全修飾ドメイン名を設定する。設定できない場合はNULL。
サーバーはこの変数を設定するべき(SHOULD)です。何かしらの理由で設定できない場合は REMOTE_ADDR と同じ値でOK。
リクエストのメソッド。必ず設定する(MUST)。
REQUEST_METHOD = method
method = "GET" | "POST" | "HEAD" | extension-method
extension-method = "PUT" | "DELETE" | token
大文字小文字が区別される点に注意。
URLエンコードされていないCGIスクリプトへのパス
サーバーのホスト名をセットする必要がある。MUST。
値はホスト名またはネットワークアドレス。
SERVER_NAME = server-name server-name = hostname | ipv4-address | ( "[" ipv6-address "]" )
SERVER_PORT = server-port server-port = 1*digit
ポート番号。
httpのようにデフォルトポートが存在し、URIにポート番号が明記されていない場合も設定する必要がある。
リクエストのプロトコル。
SERVER_PROTOCOL = HTTP-Version | "INCLUDED" | extension-version
HTTP-Version = "HTTP" "/" 1*digit "." 1*digit
extension-version = protocol [ "/" 1*digit "." 1*digit ]
protocol = token
ただし、URIのプロコトル部分と同じとは限らない。例えばこの変数の値がHTTPだとしても、HTTPSを利用している可能性があるが、スクリプトはHTTPとして扱うべきである。
CGIリクエストを作成するサーバの名前とバージョン情報。設定する必要がある(MUST)。
SERVER_SOFTWARE = 1*( product | comment )
product = token [ "/" product-version ]
product-version = token
comment = "(" *( ctext | comment ) ")"
ctext = <any TEXT excluding "(" and ")">
要求されたプロコトロルによって、そのプロトコルに依存したメタ変数をセットする必要があります。 プロコトルはメタ変数 SERVER_PROTOCOL
でセットされたものです。
サーバーはURI の <scheme>
がとは異なる SERVER_PROTOCOL
を設定することができます(NULLはダメ)。
使用されるプロトコルがHTTPの場合、 HTTP_
から始まるメタ変数はリクエストヘッダーから読み取られた値を含みます。 HTTPヘッダーフィールドは大文字に変換され、 -
はすべて _
に変換され、 ヘッダー名の先頭に HTTP_
が付け加えられます。
ヘッダーはリクエストのものをそのまま使うか、もしくは意味が変わらない範囲でサーバー側で変更しても良い。
同じヘッダー名を複数受け取った場合、サーバーはそれらを意味が変わらないように1つのメタ変数にまとめなければならない(MUST)。
同様に、サーバーは必要に応じてCGIメタ変数に適するようにデータ表現(文字コードなど)を変える必要がある(MUST)。
サーバーは受信したすべてのヘッダーフィールドに対してメタ変数を作る必要はない。 特に Authotization
や、 Content-Type
, Content-Length
はそれ用のメタ変数を使うべきで、HTTP_AUTHORIZATION
や HTTP_CONTENT_TYPE
は作らないべきである。
HTTP CGI で必要そうなメタ変数は 9.4 CGI Environment Variables - PERL IN A NUTSHELL を参考にすれば良さそう。
リクエストボディはスクリプトが標準入力を読み取ることによってデータを受け取れるようにする。
CONTENT_LENGTH
が NULL じゃない限りリクエストと共にリクエストボディが提供される(多分HTTPリクエストの話)。
サーバーは CONTENT_LENGTH
バイト読み取られた後に EOF を通知しても良いし、さらにデータをスクリプトに供給しても良い。
スクリプトは CONTENT_LENGTH
バイト以上読み取り可能であったとしてもそれ以上読み込んでは行けない(MUST NOT)。
Transfer-Encoding はスクリプトへのリクエストボディとしてサポートされていないのでサーバー側でエンコーディングを解除し、 CONTENT_LENGTH
を再計算する必要がある。 これが不可能な場合、クライアントのリクエストを拒否しても良い。
リクエストメソッドは REQUEST_METHOD
メタ変数によってスクリプトへ供給される。
RFC3875ではGET、POST、HEADが定義されている。 プロトコル特有のメソッドも実装して良い(DELETEDとかPUTとか)。
参考: NPHスクリプト
実装する必要があるかどうかは微妙。
スクリプトは必ず空ではないレスポンスを返す必要がある。
特に定義されていない限りスクリプトが標準出力に出力することでデータがサーバーへ送られる。
スクリプトは REQUEST_METHOD
を見てレスポンスを生成せねばならない。
サーバーはスクリプトからデータが送られてくるまでの時間にタイムアウトを設定しても良い。タイムアウトしたらサーバーがCGIスクリプトのプロセスを終了させることもある。
CGIのレスポンスはメッセージヘッダーとメッセージボディが含まれており、この2つは空行によって分離されている。
generic-response = 1*header-field NL [ response-body ]
CGIスクリプトが返すレスポンスには以下の種類がある。
CGI-Response = document-response | local-redir-response | client-redir-response | client-redirdoc-response
リクエストがドキュメント(HTMLとか画像とか諸々?)
document-response = Content-Type [ Status ] *other-field NL response-body
スクリプトは必ずContentTypeヘッダーを返す必要がある。
ステータスヘッダーはオプションであり、省略されている場合は 200 'OK'
が省略されていると想定する。
サーバーは必ずスクリプトの出力がクライアントへのレスポンスのプロトコル(HTTP/1.1 とか)に合うように修正しなければならない。
スクリプトはURIパスと query-string を返す(これらを合わせて 'local-pathquery' と呼ぶ)ことで、ローカルリソース(同じ仮想サーバーということかな?)への再リクエストを要求できる。
local-redir-response = local-Location NL
スクリプトは他のヘッダーフィールドまたはメッセージボディを返してはいけない。
サーバーは、以下のURLを含むリクエストに応答して生成したであろうレスポンスを生成しなければならない(MUST)。
scheme "://" server-name ":" server-port local-pathquery
スクリプトはクライアントへリダイレクトレスポンスを返すことができる。
client-redir-response = client-Location *extension-field NL
他のヘッダーフィールドを含めてはいけない。
サーバーは302でレスポンスを生成し、クライアントへ返す必要がある。
Client Redirect Response と Local Redirect Response は基本的なフォーマットは同じで、パスかドメイン名(schemeも含む?)を含むURIかを見て判断するのかな?
CGIスクリプトは、指定されたURIを使ってリクエストを再処理することをクライアントに示すために、Locationヘッダーフィールドに絶対URIパスを添付文書と一緒に返すことができます。
client-redirdoc-response = client-Location Status Content-Type *other-field NL response-body
Status は必ず指定する必要があり、 302 'Found'
が含まれている必要がある。 もしくはクライアントへのリダイレクトを表す他のステータスコードを含んでいても良い。
サーバーはスクリプトの出力がクライアントへのレスポンスのプロコトルに準拠するように変更をスクリプトの出力へ加えなければならない(MUST)。
クライアントへのレスポンスにはCGIヘッダーが1つ以上含まれている必要がある。
header-field = CGI-field | other-field
CGI-field = Content-Type | Location | Status
other-field = protocol-field | extension-field
protocol-field = generic-field
extension-field = generic-field
generic-field = field-name ":" [ field-value ] NL
field-name = token
field-value = *( field-content | LWSP )
field-content = *( token | separator | quoted-string )
フィールド名は大文字小文字区別しない。フィールド値が空のものはフィールドが指定されなかったのと同じ扱い。
CGIスクリプトの出力の各ヘッダーは1行で指定さしなくては行けない。 HTTPなどは複数の同じ名前のヘッダーがあった場合にそれらを統合して処理するが、それとは異なる。
Content-Type = "Content-Type:" media-type NL
CGIスクリプトがメッセージボディを返す場合は必ずこのヘッダーを共に出力しないといけない。
CGIスクリプトがメッセージボディを返すのにこのヘッダーを出力しない場合は、サーバー側でこのヘッダーの値を決定してはいけない。 文字セットの変更を除き、クライアントへはスクリプトの出力のまま返さないといけない。
リダイレクトを表すときに使うヘッダー。
クライアントリダイレクトの場合は <scheme>://
から始まるフルのURIを返し、ローカルリダイレクトの場合はローカルURIパスになる。
Location = local-Location | client-Location
client-Location = "Location:" fragment-URI NL
local-Location = "Location:" local-pathquery NL
fragment-URI = absoluteURI [ "#" fragment ]
fragment = *uric
local-pathquery = abs-path [ "?" query-string ]
abs-path = "/" path-segments
path-segments = segment *( "/" segment )
segment = *pchar
pchar = unreserved | escaped | extra
extra = ":" | "@" | "&" | "=" | "+" | "$" | ","
absoluteURI
の仕様は RFC 2396 - Uniform Resource Identifiers (URI): Generic Syntax 日本語訳 のセクション3 に書いてある。
HTTPレスポンスのステータスコードと同じと考えて良さそう。
Status = "Status:" status-code SP reason-phrase NL
status-code = "200" | "302" | "400" | "501" | extension-code
extension-code = 3digit
reason-phrase = *TEXT
CGIスクリプトは SERVER_PROTOCOL
に設定された値(HTTP/1.1など)に基づいてそのプロトコル固有のヘッダーを出力できる。
サーバーはCGIのヘッダーの構文とクライアントへのレスポンスのヘッダーの構文が異なる場合はヘッダーを変換しなくてはいけない。
例えば、CGIが出力で使う改行文字(LF)とHTTPレスポンスの改行文字(CRLF)が違うかもしれない。
また、スクリプトはサーバーとクライアント間の通信に影響を与えるようなヘッダーは出力してはならない (多分 Connection
ヘッダーとかかな?)。 そのようなヘッダーをCGIスクリプトが出力した場合はサーバー側で削除してもよい。
CGIスクリプトの出力ヘッダーとHTTPレスポンスヘッダーとの間に矛盾が生じた場合はそれを解決する努力をするべきである。
CGI で追加定義されたヘッダーのヘッダー名は X-CGI-
で始める必要がある。
サーバーは X-CGI-
で始まるヘッダーを認識しないあるいは削除する可能性がある。
サーバーはスクリプトが出力するメッセージボディをすべて(EOFを受け取るまで)受け取る必要がある。
スクリプトが出力したメッセージボディは HEADリクエスト、Transfer-Encoding, Content-Codings, 文字セット を除き、なるべくそのままクライアントへのレスポンスのメッセージボディにとして返すべきである。
いろんなシステムでの定義があるけど、UNIXだけ書いとく。
メタ変数は同じ名前の環境変数を用いてスクリプトに渡される。
cwd (current working directory) はスクリプトが含まれるディレクトリに設定する必要がある。
スクリプトの出力の改行コードは LF です。 サーバーは LF と更に CRLF も改行コードとして受け付け可能である必要があります。
サーバーの実装では主に以下の項目を決めておく必要があります。
- どのパスへのアクセスを許可するか。(ここは意味がよくわからなかった)
define any restrictions on allowed path segments, in particular whether non-terminal NULL segments are permitted;
-
.
や..
の動作を定義する。 それらが禁止されているか、もしくは通常のパスとして解釈されるか、もしくは相対パスとして判断するか - パスや query_string の長さをどれくらいまで許可するか。 どのくらいのヘッダーの長さまで処理するか
スクリプトが PATH_INFO
の処理を意図していない場合、 PATH_INFO
が設定されていたら 404 Not Found
をスクリプトは出力するべきである。
フォームの出力が処理される場合、CONTENT_TYPEが「application/x-www-form-urlencoded」または「multipart/form-data」であるかを確認する。CONTENT_TYPEが空白の場合、スクリプトは415 'Unsupported Media Type'エラーでリクエストを拒否できる(プロトコルでサポートされている場合)。
PATH_INFO、PATH_TRANSLATED、SCRIPT_NAMEをパースするとき、スクリプトは voidパスセグメント("//")と特殊なパスセグメント("." と "..")に注意しなければならない。これらは OS のシステムコールで使用する前にパスから削除するか、 404 'Not Found' でリクエストを拒否する必要があります。
ヘッダーフィールドを返すとき、スクリプトはできるだけ早くCGIヘッダーフィールドを送るようにし、HTTPヘッダーフィールドの前に送るようにすべきです。これは、サーバーの必要メモリを減らすのに役立つかもしれません。
スクリプトの作者は、REMOTE_ADDRとREMOTE_HOSTメタ変数(セクション4.1.8と 4.1.9参照)がリクエストの最終ソースを特定しないかもしれないことに注意す るべきである。そのクライアントは、実際のソースクライアントの代理を務めるプロキシ、 ゲートウェイ、あるいは他の仲介者かもしれない。
セキュリティ的な注意事項とかが載ってる。読んでみると面白い。