RFC 3875 The Common Gateway Interface (CGI) メモ - JUNNETWORKS/42-webserv GitHub Wiki

RFC 3875 - The Common Gateway Interface (CGI) Version 1.1 日本語訳 メモ

webservに必要そうな箇所のみ抜粋。 すべての仕様が書いてあるわけではないので注意。

3. Invoking the Script

  • サーバーはスクリプトとの間のゲートウェイとして動作する。 サーバーはクライアントからのリクエストを受け取り、そのリクエストによって適切なスクリプトを実行する。 サーバーはクライアントからのリクエストをCGIリクエストに変換し、スクリプトを実行し、スクリプトからのCGI応答をクライアントへのレスポンスへと変換する。

  • サーバーはクライアントからのリクエストによって提供されたパスによって実行するCGIを選択する。

  • サーバー側でエラー(通信周りなど)が発生した場合、サーバーは警告なしにスクリプトの実行を中断または終了させることができる。

  • Script-URI というものを定義する。

script-URI = <scheme> "://" <server-name> ":" <server-port>
                   <script-path> <extra-path> "?" <query-string>

4. The CGI Request

  • リクエストに関する情報はリクエストメタ変数とメッセージボディがある。
  • メタ変数はケースインセンシティブ(大文字小文字区別無)で、サーバーからリクエストに渡されます。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/scripthttp://host/script はどちらも QUERY_STRING= になる(NULL扱い)。
  • 値がNULLの場合はオプションのメタ変数を省略(未設定)することができます。
  • メタ変数の値は大文字小文字の区別あり。

メタ変数の紹介

以下実装する必要の有りそうなメタ変数のみ書く。全メタ変数が見たい場合は4.1参照。

参考: CGI(HTTPDヘッダー)環境変数の概要

CONTENT_LENGTH

      CONTENT_LENGTH = "" | 1*digit
  • 10進数でメッセージボディのサイズを記述する。リクエストにメッセージボディがない場合は設定しなくても良い。メッセージボディのサイズは transfer-encoding や content-codings を解除したサイズである必要がある。

CONTENT_TYPE

      CONTENT_TYPE = "" | media-type
      media-type   = type "/" subtype *( ";" parameter )
      type         = token
      subtype      = token
      parameter    = attribute "=" value
      attribute    = token
      value        = token | quoted-string
  • リクエストにメッセージボディが含まれている場合はこのメタ変数がセットされる。値は MIME のものが用いられる。
  • この変数にはデフォルト値はない。設定されてなくてもよいけど、設定されていない場合、スクリプトはエラーを返すかも。
  • 各メディアタイプは文字コードを必須パラメータとして持っている。これは以下のルールによって定められる。
    1. メディアタイプに対してシステム定義のデフォルトの文字コードがある。
    2. text メディアタイプのデフォルト文字コードは ISO-8859-1
    3. メディアタイプの仕様で文字コードが定義されている。
    4. デフォルトの文字コードは US-ASCII である。
  • HTTP Content-Type がリクエストに設定されている場合は必ずこの変数に値をセットする必要がある。
  • リクエストボディがあったが、Content-Type がなかった場合、サーバーは正しい Content-Type を決定する努力をしなければならない。さもなければこのメタ変数を省略する必要がある。

GATEWAY_INTERFACE

  • スクリプトと通信するための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_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_TRANSLATED

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 となる。

QUERY_STRING

この変数にはパーセントエンコーディングされたパラメータ文字列が含まれます。

Script-URI でいう <query_string> の部分がこの変数にセットされる。

サーバーはこの変数を設定する必要がある。URLパラメータが含まれていない場合は QUERY_STRING= のように空文字列である必要がある。

REMOTE_ADDR

      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

      REMOTE_HOST   = "" | hostname | hostnumber
      hostname      = *( domainlabel "." ) toplabel [ "." ]
      domainlabel   = alphanum [ *alphahypdigit alphanum ]
      toplabel      = alpha [ *alphahypdigit alphanum ]
      alphahypdigit = alphanum | "-"

完全修飾ドメイン名を設定する。設定できない場合はNULL。

サーバーはこの変数を設定するべき(SHOULD)です。何かしらの理由で設定できない場合は REMOTE_ADDR と同じ値でOK。

REQUEST_METHOD

リクエストのメソッド。必ず設定する(MUST)。

      REQUEST_METHOD   = method
      method           = "GET" | "POST" | "HEAD" | extension-method
      extension-method = "PUT" | "DELETE" | token

大文字小文字が区別される点に注意。

SCRIPT_NAME

URLエンコードされていないCGIスクリプトへのパス

SERVER_NAME

サーバーのホスト名をセットする必要がある。MUST。

値はホスト名またはネットワークアドレス。

      SERVER_NAME = server-name server-name = hostname | ipv4-address | ( "[" ipv6-address "]" )

SERVER_PORT

      SERVER_PORT = server-port server-port = 1*digit

ポート番号。

httpのようにデフォルトポートが存在し、URIにポート番号が明記されていない場合も設定する必要がある。

SERVER_PROTOCOL

リクエストのプロトコル。

      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として扱うべきである。

SERVER_SOFTWARE

CGIリクエストを作成するサーバの名前とバージョン情報。設定する必要がある(MUST)。

      SERVER_SOFTWARE = 1*( product | comment )
      product         = token [ "/" product-version ]
      product-version = token
      comment         = "(" *( ctext | comment ) ")"
      ctext           = <any TEXT excluding "(" and ")">

HTTP 特有のメタ変数

要求されたプロコトロルによって、そのプロトコルに依存したメタ変数をセットする必要があります。 プロコトルはメタ変数 SERVER_PROTOCOL でセットされたものです。

サーバーはURI の <scheme> がとは異なる SERVER_PROTOCOL を設定することができます(NULLはダメ)。

使用されるプロトコルがHTTPの場合、 HTTP_ から始まるメタ変数はリクエストヘッダーから読み取られた値を含みます。 HTTPヘッダーフィールドは大文字に変換され、 - はすべて _ に変換され、 ヘッダー名の先頭に HTTP_ が付け加えられます。

ヘッダーはリクエストのものをそのまま使うか、もしくは意味が変わらない範囲でサーバー側で変更しても良い。

同じヘッダー名を複数受け取った場合、サーバーはそれらを意味が変わらないように1つのメタ変数にまとめなければならない(MUST)。

同様に、サーバーは必要に応じてCGIメタ変数に適するようにデータ表現(文字コードなど)を変える必要がある(MUST)。

サーバーは受信したすべてのヘッダーフィールドに対してメタ変数を作る必要はない。 特に Authotization や、 Content-Type, Content-Length はそれ用のメタ変数を使うべきで、HTTP_AUTHORIZATIONHTTP_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

リクエストメソッドは REQUEST_METHOD メタ変数によってスクリプトへ供給される。

RFC3875ではGET、POST、HEADが定義されている。 プロトコル特有のメソッドも実装して良い(DELETEDとかPUTとか)。

NPHスクリプト

参考: NPHスクリプト

実装する必要があるかどうかは微妙。

Section6 CGI Response

スクリプトは必ず空ではないレスポンスを返す必要がある。

特に定義されていない限りスクリプトが標準出力に出力することでデータがサーバーへ送られる。

スクリプトは REQUEST_METHOD を見てレスポンスを生成せねばならない。

サーバーはスクリプトからデータが送られてくるまでの時間にタイムアウトを設定しても良い。タイムアウトしたらサーバーがCGIスクリプトのプロセスを終了させることもある。

Response Types

CGIのレスポンスはメッセージヘッダーとメッセージボディが含まれており、この2つは空行によって分離されている。

      generic-response = 1*header-field NL [ response-body ]

CGIスクリプトが返すレスポンスには以下の種類がある。

      CGI-Response = document-response | local-redir-response | client-redir-response | client-redirdoc-response

Document Response

リクエストがドキュメント(HTMLとか画像とか諸々?)

      document-response = Content-Type [ Status ] *other-field NL response-body

スクリプトは必ずContentTypeヘッダーを返す必要がある。

ステータスヘッダーはオプションであり、省略されている場合は 200 'OK' が省略されていると想定する。

サーバーは必ずスクリプトの出力がクライアントへのレスポンスのプロトコル(HTTP/1.1 とか)に合うように修正しなければならない。

Local Redirect Response

スクリプトはURIパスと query-string を返す(これらを合わせて 'local-pathquery' と呼ぶ)ことで、ローカルリソース(同じ仮想サーバーということかな?)への再リクエストを要求できる。

      local-redir-response = local-Location NL

スクリプトは他のヘッダーフィールドまたはメッセージボディを返してはいけない。

サーバーは、以下のURLを含むリクエストに応答して生成したであろうレスポンスを生成しなければならない(MUST)。

      scheme "://" server-name ":" server-port local-pathquery

Client Redirect Response

スクリプトはクライアントへリダイレクトレスポンスを返すことができる。

      client-redir-response = client-Location *extension-field NL

他のヘッダーフィールドを含めてはいけない。

サーバーは302でレスポンスを生成し、クライアントへ返す必要がある。

Client Redirect Response と Local Redirect Response は基本的なフォーマットは同じで、パスかドメイン名(schemeも含む?)を含むURIかを見て判断するのかな?

Client Redirect Response with Document

CGIスクリプトは、指定されたURIを使ってリクエストを再処理することをクライアントに示すために、Locationヘッダーフィールドに絶対URIパスを添付文書と一緒に返すことができます。

      client-redirdoc-response = client-Location Status Content-Type *other-field NL response-body

Status は必ず指定する必要があり、 302 'Found' が含まれている必要がある。 もしくはクライアントへのリダイレクトを表す他のステータスコードを含んでいても良い。

サーバーはスクリプトの出力がクライアントへのレスポンスのプロコトルに準拠するように変更をスクリプトの出力へ加えなければならない(MUST)。

6.3 Response Header Fields

クライアントへのレスポンスには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などは複数の同じ名前のヘッダーがあった場合にそれらを統合して処理するが、それとは異なる。

6.3.1. Content-Type

      Content-Type = "Content-Type:" media-type NL

CGIスクリプトがメッセージボディを返す場合は必ずこのヘッダーを共に出力しないといけない。

CGIスクリプトがメッセージボディを返すのにこのヘッダーを出力しない場合は、サーバー側でこのヘッダーの値を決定してはいけない。 文字セットの変更を除き、クライアントへはスクリプトの出力のまま返さないといけない。

6.3.2. Location

リダイレクトを表すときに使うヘッダー。

クライアントリダイレクトの場合は <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 に書いてある。

6.3.3. Status

HTTPレスポンスのステータスコードと同じと考えて良さそう。

      Status         = "Status:" status-code SP reason-phrase NL
      status-code    = "200" | "302" | "400" | "501" | extension-code
      extension-code = 3digit
      reason-phrase  = *TEXT

6.3.4. Protocol-Specific Header Fields

CGIスクリプトは SERVER_PROTOCOL に設定された値(HTTP/1.1など)に基づいてそのプロトコル固有のヘッダーを出力できる。

サーバーはCGIのヘッダーの構文とクライアントへのレスポンスのヘッダーの構文が異なる場合はヘッダーを変換しなくてはいけない。

例えば、CGIが出力で使う改行文字(LF)とHTTPレスポンスの改行文字(CRLF)が違うかもしれない。

また、スクリプトはサーバーとクライアント間の通信に影響を与えるようなヘッダーは出力してはならない (多分 Connection ヘッダーとかかな?)。 そのようなヘッダーをCGIスクリプトが出力した場合はサーバー側で削除してもよい。

CGIスクリプトの出力ヘッダーとHTTPレスポンスヘッダーとの間に矛盾が生じた場合はそれを解決する努力をするべきである。

6.3.5. Extension Header Fields

CGI で追加定義されたヘッダーのヘッダー名は X-CGI- で始める必要がある。

サーバーは X-CGI- で始まるヘッダーを認識しないあるいは削除する可能性がある。

6.4. Response Message-Body

サーバーはスクリプトが出力するメッセージボディをすべて(EOFを受け取るまで)受け取る必要がある。

スクリプトが出力したメッセージボディは HEADリクエスト、Transfer-Encoding, Content-Codings, 文字セット を除き、なるべくそのままクライアントへのレスポンスのメッセージボディにとして返すべきである。

7. System Specifications

いろんなシステムでの定義があるけど、UNIXだけ書いとく。

7.2. UNIX

メタ変数は同じ名前の環境変数を用いてスクリプトに渡される。

cwd (current working directory) はスクリプトが含まれるディレクトリに設定する必要がある。

スクリプトの出力の改行コードは LF です。 サーバーは LF と更に CRLF も改行コードとして受け付け可能である必要があります。

8. Implementation

8.1. Recommendations for Servers

サーバーの実装では主に以下の項目を決めておく必要があります。

  • どのパスへのアクセスを許可するか。(ここは意味がよくわからなかった) define any restrictions on allowed path segments, in particular whether non-terminal NULL segments are permitted;
  • ... の動作を定義する。 それらが禁止されているか、もしくは通常のパスとして解釈されるか、もしくは相対パスとして判断するか
  • パスや query_string の長さをどれくらいまで許可するか。 どのくらいのヘッダーの長さまで処理するか

8.2. Recommendations for Scripts

スクリプトが 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参照)がリクエストの最終ソースを特定しないかもしれないことに注意す るべきである。そのクライアントは、実際のソースクライアントの代理を務めるプロキシ、 ゲートウェイ、あるいは他の仲介者かもしれない。

9. Security Considerations

セキュリティ的な注意事項とかが載ってる。読んでみると面白い。

⚠️ **GitHub.com Fallback** ⚠️